tests
[m6w6/ascertain] / lib / ascertain / Assert.php
1 <?php
2
3 namespace ascertain;
4
5 /**
6 * Be sure to always pass an error string as last argument to the assertion:
7 * <code>
8 * $testable->assert()
9 * ->that("name")->isLen(3, 40, "must be between 3 and 40 characters long")
10 * ->that("email")->isEmail("is not valid");
11 * </code>
12 */
13 class Assert
14 {
15 /**
16 * Testable properties
17 * @var array
18 */
19 private $properties;
20
21 /**
22 * The name of the currently inspected property
23 * @var string
24 */
25 private $inspectedProperty;
26
27 /**
28 * Whether the argument to when() was true
29 * @var bool
30 */
31 private $inspectCondition = true;
32
33 /**
34 * Whether assertions cause exceptions of this type
35 * @var string
36 */
37 private $exceptionClass;
38
39 /**
40 * Failed assertions
41 * @var array
42 */
43 private $validationErrors = array();
44
45 /**
46 * Succeeded assertions
47 * @var array
48 */
49 private $validationResults = array();
50
51 /**
52 * @param \ascertain\Testable $testable
53 * @param string $exceptionClass
54 */
55 function __construct(Testable $testable, $exceptionClass = null) {
56 $this->properties = $testable->export();
57 $this->exceptionClass = $exceptionClass;
58 }
59
60 /**
61 * @return int count of failed assertions
62 */
63 function hasErrors() {
64 return count($this->validationErrors);
65 }
66
67 /**
68 * @return array of failed assertions
69 */
70 function getErrors() {
71 return $this->validationErrors;
72 }
73
74 /**
75 * Reset assertions failures/results
76 * @return \ascertain\Assert
77 */
78 function resetErrors() {
79 $this->validationResults = array();
80 $this->validationErrors = array();
81 return $this;
82 }
83
84 /**
85 * Set the currently inspected property
86 * @param string $property
87 * @return \ascertain\Assert
88 */
89 function that($property) {
90 $this->inspectCondition = true;
91 $this->inspectedProperty = $property;
92 return $this;
93 }
94
95 /**
96 * The following assertions are only tested if the argument is true
97 * @param bool $condition
98 * @return \ascertain\Assert
99 */
100 function when($condition) {
101 $this->inspectCondition = $condition;
102 return $this;
103 }
104
105 /**
106 * !strcmp($v, $c)
107 * @param mixed &$r
108 * @param string $v
109 * @param string $c
110 * @return bool
111 */
112 function test(&$r, $v, $c) {
113 $r = !strcmp($v, $c);
114 return $r;
115 }
116
117 /**
118 * !strlen($v)
119 * @param mixed &$r
120 * @param string $v
121 * @return bool
122 */
123 function testNothing(&$r, $v) {
124 $r = !strlen($v);
125 return $r;
126 }
127
128 /**
129 * is_numeric($v)
130 * @param mixed &$r
131 * @param string $v
132 * @return bool
133 */
134 function testNumeric(&$r, $v) {
135 $r = is_numeric($v);
136 return $r;
137 }
138
139 /**
140 * Test whether the argument is scalar
141 * @param mixed &$r
142 * @param mixed $v
143 * @param bool $strictNulls
144 * @return bool
145 */
146 function testScalar(&$r, $v, $strictNulls = false) {
147 $r = is_scalar($v) && (!$strictNulls || !isset($v));
148 return $r;
149 }
150
151 /**
152 * Test wheter the argument constists only of printable characters
153 * @param mixed &$r
154 * @param string $v
155 * @return bool
156 */
157 function testPrintable(&$r, $v) {
158 return preg_match("/^[[:print:]\\P{Cc}]*\$/u", $v, $r) > 0;
159 }
160
161 /**
162 * Test wheter the string length is between a certain range
163 * @param mixed &$r
164 * @param string $v
165 * @param int $min
166 * @param int $max
167 * @return bool
168 */
169 function testLen(&$r, $v, $min, $max = PHP_INT_MAX) {
170 return $this->testRange($r, function_exists("mb_strlen") ? mb_strlen($v) : strlen($v), $min, $max);
171 }
172
173 /**
174 * Test wheter a value is between a certain range
175 * @param mixed &$r
176 * @param mixed $v
177 * @param mixed $min
178 * @param mixed $max
179 * @return bool
180 */
181 function testRange(&$r, $v, $min, $max) {
182 $r = (($v >= $min) && ($v <= $max));
183 return $r;
184 }
185
186 /**
187 * Test for a valid integer with FILTER_VALIDATE_INT
188 * @param mixed &$r
189 * @param mixed $v
190 * @param array $options
191 * @return bool
192 */
193 function testInteger(&$r, $v, array $options = null) {
194 $r = filter_var($v, FILTER_VALIDATE_INT, $options);
195 return $r !== false;
196 }
197
198 /**
199 * Test for a valid boolean with FILTER_VALIDATE_BOOLEAN
200 * @param mixed &$r
201 * @param type $v
202 * @param array $options
203 * @return type
204 */
205 function testBoolean(&$r, $v, array $options = null) {
206 $options["flags"] = isset($options["flags"]) ? $options["flags"]|FILTER_NULL_ON_FAILURE : FILTER_NULL_ON_FAILURE;
207 $r = filter_var($v, FILTER_VALIDATE_BOOLEAN, $options);
208 return isset($r);
209 }
210
211 /**
212 * Test for a valid float with FILTER_VALIDATE_FLOAT
213 * @param mixed &$r
214 * @param mixed $v
215 * @param array $options
216 * @return bool
217 */
218 function testFloat(&$r, $v, array $options = null) {
219 $r = filter_var($v, FILTER_VALIDATE_FLOAT, $options);
220 return $r !== false;
221 }
222
223 /**
224 * Test for a valid URL with FILTER_VALIDATE_URL
225 * @param mixed &$r
226 * @param string $v
227 * @param array $options
228 * @return bool
229 */
230 function testUrl(&$r, $v, array $options = null) {
231 $r = filter_var($v, FILTER_VALIDATE_URL, $options);
232 return $r !== false;
233 }
234
235 /**
236 * Test for a valid email address with FILTER_VALIDATE_EMAIL
237 * @param mixed &$r
238 * @param string $v
239 * @param array $options
240 * @return bool
241 */
242 function testEmail(&$r, $v, array $options = null) {
243 $r = filter_var($v, FILTER_VALIDATE_EMAIL, $options);
244 return $r !== false;
245 }
246
247 /**
248 * Test for a valid IP address with FILTER_VALIDATE_IP
249 * @param mixed &$r
250 * @param string $v
251 * @param array $options
252 * @return bool
253 */
254 function testIp(&$r, $v, array $options = null) {
255 $r = filter_var($v, FILTER_VALIDATE_IP, $options);
256 return $r !== false;
257 }
258
259 /**
260 * Test whether a string contains another string or an array contains a key
261 * @param mixed &$r
262 * @param mixed $v haystack
263 * @param string $n needle
264 * @param bool $cs case-sensitive
265 * @return bool
266 */
267 function testContaining(&$r, $v, $n, $cs = true) {
268 if (is_array($v)) {
269 if (!$cs) {
270 $v = array_change_key_case($v);
271 $n = strtolower($n);
272 }
273 if (array_key_exists($n, $v)) {
274 $r = $v[$n];
275 return true;
276 }
277 return $r = false;
278 } else {
279 if ($cs) {
280 $r = strstr($v, (string) $n);
281 } else {
282 $r = stristr($v, (string) $n);
283 }
284 return $r !== false;
285 }
286 }
287
288 /**
289 * Thest
290 * @param mixed &$r
291 * @param mixed $v
292 * @param array $a
293 * @param bool $strict
294 * @return bool
295 */
296 function testAny(&$r, $v, $a, $strict = false) {
297 return $r = in_array($v, $a, $strict);
298 }
299
300 /**
301 * Thest if a regular expression matches
302 * @param mixed &$r
303 * @param string $v
304 * @param stirng $regex
305 * @param int $flags
306 * @return bool
307 */
308 function testMatching(&$r, $v, $regex, $flags = 0) {
309 return preg_match($regex, $v, $r, $flags) > 0;
310 }
311
312 /**
313 * Semantic is(Not) wrapper to the assertions
314 * @param string $method
315 * @param array $args
316 * @return \ascertain\Assert
317 * @throws InvalidArgumentException (or rahter the configured exception)
318 */
319 function __call($method, $args) {
320 $match = null;
321 if ($this->inspectCondition && preg_match("/^is(Not(?!hing))?(.*)\$/i", $method, $match)) {
322 list(, $not, $test) = $match;
323
324 $result = null;
325 $error = array_pop($args);
326 array_unshift($args, $this->properties[$this->inspectedProperty]);
327 array_unshift($args, null);
328 $args[0] = &$result;
329 $valid = call_user_func_array(array($this, "test$test"), $args);
330 if ($valid === !!$not) {
331 $this->validationErrors[$this->inspectedProperty][] = $error;
332 if (($exception = $this->exceptionClass)) {
333 throw new $exception("$this->inspectedProperty $error");
334 }
335 }
336 $this->validationResults[$this->inspectedProperty][] = $args[0];
337 }
338 return $this;
339 }
340
341 }