summaryrefslogtreecommitdiff
path: root/vendor/wikimedia/assert/src/Assert.php
blob: 53b438a5bc26542e945b28671ecaae5a876d1161 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php

namespace Wikimedia\Assert;

/**
 * Assert provides functions for assorting preconditions (such as parameter types) and
 * postconditions. It is intended as a safer alternative to PHP's assert() function.
 *
 * Note that assertions evaluate expressions and add function calls, so using assertions
 * may have a negative impact on performance when used in performance hotspots. The idea
 * if this class is to have a neat tool for assertions if and when they are needed.
 * It is not recommended to place assertions all over the code indiscriminately.
 *
 * For more information, see the the README file.
 *
 * @license MIT
 * @author Daniel Kinzler
 * @copyright Wikimedia Deutschland e.V.
 */
class Assert {

	/**
	 * Checks a precondition, that is, throws a PreconditionException if $condition is false.
	 * For checking call parameters, use Assert::parameter() instead.
	 *
	 * This is provided for completeness, most preconditions should be covered by
	 * Assert::parameter() and related assertions.
	 *
	 * @see parameter()
	 *
	 * @note This is intended mostly for checking preconditions in constructors and setters,
	 * or before using parameters in complex computations.
	 * Checking preconditions in every function call is not recommended, since it may have a
	 * negative impact on performance.
	 *
	 * @param bool $condition
	 * @param string $description The message to include in the exception if the condition fails.
	 *
	 * @throws PreconditionException if $condition is not true.
	 */
	public static function precondition( $condition, $description ) {
		if ( !$condition ) {
			throw new PreconditionException( "Precondition failed: $description" );
		}
	}

	/**
	 * Checks a parameter, that is, throws a ParameterAssertionException if $condition is false.
	 * This is similar to Assert::precondition().
	 *
	 * @note This is intended for checking parameters in constructors and setters.
	 * Checking parameters in every function call is not recommended, since it may have a
	 * negative impact on performance.
	 *
	 * @param bool $condition
	 * @param string $name The name of the parameter that was checked.
	 * @param string $description The message to include in the exception if the condition fails.
	 *
	 * @throws ParameterAssertionException if $condition is not true.
	 */
	public static function parameter( $condition, $name, $description ) {
		if ( !$condition ) {
			throw new ParameterAssertionException( $name, $description );
		}
	}

	/**
	 * Checks an parameter's type, that is, throws a InvalidArgumentException if $condition is false.
	 * This is really a special case of Assert::precondition().
	 *
	 * @note This is intended for checking parameters in constructors and setters.
	 * Checking parameters in every function call is not recommended, since it may have a
	 * negative impact on performance.
	 *
	 * @note If possible, type hints should be used instead of calling this function.
	 * It is intended for cases where type hints to not work, e.g. for checking primitive types.
	 *
	 * @param string $type The parameter's expected type. Can be the name of a native type or a
	 *        class or interface. If multiple types are allowed, they can be given separated by
	 *        a pipe character ("|").
	 * @param mixed $value The parameter's actual value.
	 * @param string $name The name of the parameter that was checked.
	 *
	 * @throws ParameterTypeException if $value is not of type (or, for objects, is not an
	 *         instance of) $type.
	 */
	public static function parameterType( $type, $value, $name ) {
		if ( !self::hasType( $value, explode( '|', $type ) ) ) {
			throw new ParameterTypeException( $name, $type );
		}
	}

	/**
	 * Checks the type of all elements of an parameter, assuming the parameter is an array,
	 * that is, throws a ParameterElementTypeException if $value
	 *
	 * @note This is intended for checking parameters in constructors and setters.
	 * Checking parameters in every function call is not recommended, since it may have a
	 * negative impact on performance.
	 *
	 * @param string $type The elements' expected type. Can be the name of a native type or a
	 *        class or interface. If multiple types are allowed, they can be given separated by
	 *        a pipe character ("|").
	 * @param mixed $value The parameter's actual value. If this is not an array,
	 *        a ParameterTypeException is raised.
	 * @param string $name The name of the parameter that was checked.
	 *
	 * @throws ParameterTypeException If $value is not an array.
	 * @throws ParameterElementTypeException If an element of $value  is not of type
	 *         (or, for objects, is not an instance of) $type.
	 */
	public static function parameterElementType( $type, $value, $name ) {
		self::parameterType( 'array', $value, $name );

		$allowedTypes = explode( '|', $type );

		foreach ( $value as $element ) {
			if ( !self::hasType( $element, $allowedTypes ) ) {
				throw new ParameterElementTypeException( $name, $type );
			}
		}
	}

	/**
	 * Checks a postcondition, that is, throws a PostconditionException if $condition is false.
	 * This is very similar Assert::invariant() but is intended for use only after a computation
	 * is complete.
	 *
	 * @note This is intended for sanity-checks in the implementation of complex algorithms.
	 * Note however that it should not be used in performance hotspots, since evaluating
	 * $condition and calling postcondition() costs time.
	 *
	 * @param bool $condition
	 * @param string $description The message to include in the exception if the condition fails.
	 *
	 * @throws PostconditionException
	 */
	public static function postcondition( $condition, $description ) {
		if ( !$condition ) {
			throw new PostconditionException( "Postcondition failed: $description" );
		}
	}

	/**
	 * Checks an invariant, that is, throws a InvariantException if $condition is false.
	 * This is very similar Assert::postcondition() but is intended for use throughout the code.
	 *
	 * @note This is intended for sanity-checks in the implementation of complex algorithms.
	 * Note however that it should not be used in performance hotspots, since evaluating
	 * $condition and calling postcondition() costs time.
	 *
	 * @param bool $condition
	 * @param string $description The message to include in the exception if the condition fails.
	 *
	 * @throws InvariantException
	 */
	public static function invariant( $condition, $description ) {
		if ( !$condition ) {
			throw new InvariantException( "Invariant failed: $description" );
		}
	}

	/**
	 * @param mixed $value
	 * @param array $allowedTypes
	 *
	 * @return bool
	 */
	private static function hasType( $value, array $allowedTypes ) {
		// Apply strtolower because gettype returns "NULL" for null values.
		$type = strtolower( gettype( $value ) );

		if ( in_array( $type, $allowedTypes ) ) {
			return true;
		}

		if ( is_callable( $value ) && in_array( 'callable', $allowedTypes ) ) {
			return true;
		}

		if ( is_object( $value ) && self::isInstanceOf( $value, $allowedTypes ) ) {
			return true;
		}

		return false;
	}

	/**
	 * @param mixed $value
	 * @param array $allowedTypes
	 *
	 * @return bool
	 */
	private static function isInstanceOf( $value, array $allowedTypes ) {
		foreach ( $allowedTypes as $type ) {
			if ( $value instanceof $type ) {
				return true;
			}
		}

		return false;
	}

}