1 : <?php
2 : /**
3 : * Mockery
4 : *
5 : * LICENSE
6 : *
7 : * This source file is subject to the new BSD license that is bundled
8 : * with this package in the file LICENSE.txt.
9 : * It is also available through the world-wide-web at this URL:
10 : * http://github.com/padraic/mockery/blob/master/LICENSE
11 : * If you did not receive a copy of the license and are unable to
12 : * obtain it through the world-wide-web, please send an email
13 : * to padraic@php.net so we can send you a copy immediately.
14 : *
15 : * @category Mockery
16 : * @package Mockery
17 : * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
18 : * @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
19 : */
20 :
21 : namespace Mockery;
22 :
23 : class Expectation
24 : {
25 :
26 : /**
27 : * Mock object to which this expectation belongs
28 : *
29 : * @var object
30 : */
31 : protected $_mock = null;
32 :
33 : /**
34 : * Method name
35 : *
36 : * @var string
37 : */
38 : protected $_name = null;
39 :
40 : /**
41 : * Arguments expected by this expectation
42 : *
43 : * @var array
44 : */
45 : protected $_expectedArgs = array();
46 :
47 : /**
48 : * Count validator store
49 : *
50 : * @var array
51 : */
52 : protected $_countValidators = array();
53 :
54 : /**
55 : * The count validator class to use
56 : *
57 : * @var string
58 : */
59 : protected $_countValidatorClass = 'Mockery\CountValidator\Exact';
60 :
61 : /**
62 : * Actual count of calls to this expectation
63 : *
64 : * @var int
65 : */
66 : protected $_actualCount = 0;
67 :
68 : /**
69 : * Value to return from this expectation
70 : *
71 : * @var mixed
72 : */
73 : protected $_returnValue = null;
74 :
75 : /**
76 : * Array of return values as a queue for multiple return sequence
77 : *
78 : * @var array
79 : */
80 : protected $_returnQueue = array();
81 :
82 : /**
83 : * Array of closures executed with given arguments to generate a result
84 : * to be returned
85 : *
86 : * @var array
87 : */
88 : protected $_closureQueue = array();
89 :
90 : /**
91 : * Integer representing the call order of this expectation
92 : *
93 : * @var int
94 : */
95 : protected $_orderNumber = null;
96 :
97 : /**
98 : * Integer representing the call order of this expectation on a global basis
99 : *
100 : * @var int
101 : */
102 : protected $_globalOrderNumber = null;
103 :
104 : /**
105 : * Flag indicating that an exception is expected to be throw (not returned)
106 : *
107 : * @var bool
108 : */
109 : protected $_throw = false;
110 :
111 : /**
112 : * Flag indicating whether the order of calling is determined locally or
113 : * globally
114 : *
115 : * @var bool
116 : */
117 : protected $_globally = false;
118 :
119 : /**
120 : * Flag indicating we expect no arguments
121 : *
122 : * @var bool
123 : */
124 : protected $_noArgsExpectation = false;
125 :
126 : /**
127 : * Constructor
128 : *
129 : * @param \Mockery\MockInterface $mock
130 : * @param string $name
131 : */
132 : public function __construct(\Mockery\MockInterface $mock, $name)
133 : {
134 26 : $this->_mock = $mock;
135 26 : $this->_name = $name;
136 26 : }
137 :
138 : /**
139 : * Return a string with the method name and arguments formatted
140 : *
141 : * @param string $name Name of the expected method
142 : * @param array $args List of arguments to the method
143 : * @return string
144 : */
145 : public function __toString()
146 : {
147 0 : return \Mockery::formatArgs($this->_name, $this->_expectedArgs);
148 : }
149 :
150 : /**
151 : * Verify the current call, i.e. that the given arguments match those
152 : * of this expectation
153 : *
154 : * @param array $args
155 : * @return mixed
156 : */
157 : public function verifyCall(array $args)
158 : {
159 25 : $this->validateOrder();
160 25 : $this->_actualCount++;
161 25 : $return = $this->_getReturnValue($args);
162 25 : if ($return instanceof \Exception && $this->_throw === true) {
163 0 : throw $return;
164 : }
165 25 : return $return;
166 : }
167 :
168 : /**
169 : * Fetch the return value for the matching args
170 : *
171 : * @param array $args
172 : * @return mixed
173 : */
174 : protected function _getReturnValue(array $args)
175 : {
176 25 : if (count($this->_closureQueue) > 1) {
177 0 : return call_user_func_array(array_shift($this->_closureQueue), $args);
178 25 : } elseif (count($this->_closureQueue) > 0) {
179 0 : return call_user_func_array(current($this->_closureQueue), $args);
180 25 : } elseif (count($this->_returnQueue) > 1) {
181 0 : return array_shift($this->_returnQueue);
182 25 : } elseif (count($this->_returnQueue) > 0) {
183 25 : return current($this->_returnQueue);
184 : }
185 0 : }
186 :
187 : /**
188 : * Checks if this expectation is eligible for additional calls
189 : *
190 : * @return bool
191 : */
192 : public function isEligible()
193 : {
194 25 : foreach ($this->_countValidators as $validator) {
195 24 : if (!$validator->isEligible($this->_actualCount)) {
196 0 : return false;
197 : }
198 25 : }
199 25 : return true;
200 : }
201 :
202 : /**
203 : * Check if there is a constraint on call count
204 : *
205 : * @return bool
206 : */
207 : public function isCallCountConstrained()
208 : {
209 0 : return (count($this->_countValidators) > 0);
210 : }
211 :
212 : /**
213 : * Verify call order
214 : *
215 : * @return void
216 : */
217 : public function validateOrder()
218 : {
219 25 : if ($this->_orderNumber) {
220 0 : $this->_mock->mockery_validateOrder((string) $this, $this->_orderNumber);
221 0 : }
222 25 : if ($this->_globalOrderNumber) {
223 0 : $this->_mock->mockery_getContainer()
224 0 : ->mockery_validateOrder((string) $this, $this->_globalOrderNumber);
225 0 : }
226 25 : }
227 :
228 : /**
229 : * Verify this expectation
230 : *
231 : * @return bool
232 : */
233 : public function verify()
234 : {
235 24 : foreach ($this->_countValidators as $validator) {
236 23 : $validator->validate($this->_actualCount);
237 24 : }
238 24 : }
239 :
240 : /**
241 : * Check if passed arguments match an argument expectation
242 : *
243 : * @param array $args
244 : * @return bool
245 : */
246 : public function matchArgs(array $args)
247 : {
248 25 : if(empty($this->_expectedArgs) && !$this->_noArgsExpectation) {
249 0 : return true;
250 : }
251 25 : if(count($args) !== count($this->_expectedArgs)) {
252 0 : return false;
253 : }
254 25 : for ($i=0; $i<count($args); $i++) {
255 25 : $param =& $args[$i];
256 25 : if (!$this->_matchArg($this->_expectedArgs[$i], $param)) {
257 2 : return false;
258 : }
259 25 : }
260 25 : return true;
261 : }
262 :
263 : /**
264 : * Check if passed argument matches an argument expectation
265 : *
266 : * @param array $args
267 : * @return bool
268 : */
269 : protected function _matchArg($expected, &$actual)
270 : {
271 25 : if ($expected === $actual) {
272 25 : return true;
273 : }
274 8 : if (!is_object($expected) && !is_object($actual) && $expected == $actual) {
275 0 : return true;
276 : }
277 8 : if (is_string($expected) && !is_array($actual) && !is_object($actual)) {
278 2 : $result = @preg_match($expected, (string) $actual);
279 2 : if($result) {
280 0 : return true;
281 : }
282 2 : }
283 8 : if (is_string($expected) && is_object($actual)) {
284 0 : $result = $actual instanceof $expected;
285 0 : if($result) {
286 0 : return true;
287 : }
288 0 : }
289 8 : if ($expected instanceof \Mockery\Matcher\MatcherAbstract) {
290 7 : return $expected->match($actual);
291 : }
292 2 : if ($expected instanceof \Hamcrest_Matcher) {
293 0 : return $expected->matches($actual);
294 : }
295 2 : return false;
296 : }
297 :
298 : /**
299 : * Expected argument setter for the expectation
300 : *
301 : * @param mixed
302 : * @return self
303 : */
304 : public function with()
305 : {
306 25 : $this->_expectedArgs = func_get_args();
307 25 : return $this;
308 : }
309 :
310 : /**
311 : * Set with() as no arguments expected
312 : *
313 : * @return self
314 : */
315 : public function withNoArgs()
316 : {
317 0 : $this->_noArgsExpectation = true;
318 0 : return $this->with();
319 : }
320 :
321 : /**
322 : * Set expectation that any arguments are acceptable
323 : *
324 : * @return self
325 : */
326 : public function withAnyArgs()
327 : {
328 0 : $this->_expectedArgs = array();
329 0 : return $this;
330 : }
331 :
332 : /**
333 : * Set a return value, or sequential queue of return values
334 : *
335 : * @return self
336 : */
337 : public function andReturn()
338 : {
339 25 : $this->_returnQueue = func_get_args();
340 25 : return $this;
341 : }
342 :
343 : /**
344 : * Set a closure or sequence of closures with which to generate return
345 : * values. The arguments passed to the expected method are passed to the
346 : * closures as parameters.
347 : *
348 : * @return self
349 : */
350 : public function andReturnUsing()
351 : {
352 0 : $this->_closureQueue = func_get_args();
353 0 : return $this;
354 : }
355 :
356 : /**
357 : * Return a self-returning black hole object.
358 : *
359 : * @return self
360 : */
361 : public function andReturnUndefined()
362 : {
363 0 : $this->andReturn(new \Mockery\Undefined);
364 0 : return $this;
365 : }
366 :
367 : /**
368 : * Set Exception class and arguments to that class to be thrown
369 : *
370 : * @param string $exception
371 : * @param string $message
372 : * @param int $code
373 : * @param Exception $previous
374 : * @return self
375 : */
376 : public function andThrow($exception, $message = '', $code = 0, \Exception $previous = null)
377 : {
378 0 : $this->_throw = true;
379 0 : if (is_object($exception)) {
380 0 : $this->andReturn($exception);
381 0 : } else {
382 0 : $this->andReturn(new $exception($message, $code, $previous));
383 : }
384 0 : return $this;
385 : }
386 :
387 : /**
388 : * Set a public property on the mock
389 : *
390 : * @param string $name
391 : * @param mixed $value
392 : * @return self
393 : */
394 : public function andSet($name, $value)
395 : {
396 0 : $this->_mock->{$name} = $value;
397 0 : return $this;
398 : }
399 :
400 : /**
401 : * Set a public property on the mock (alias to andSet()). Allows the natural
402 : * English construct - set('foo', 'bar')->andReturn('bar')
403 : *
404 : * @param string $name
405 : * @param mixed $value
406 : * @return self
407 : */
408 : public function set($name, $value)
409 : {
410 0 : return $this->andSet($name, $value);
411 : }
412 :
413 : /**
414 : * Indicates this expectation should occur zero or more times
415 : *
416 : * @return self
417 : */
418 : public function zeroOrMoreTimes()
419 : {
420 0 : $this->atLeast()->never();
421 0 : }
422 :
423 : /**
424 : * Indicates the number of times this expectation should occur
425 : *
426 : * @param int $limit
427 : */
428 : public function times($limit = null)
429 : {
430 25 : if (is_null($limit)) return $this;
431 25 : $this->_countValidators[] = new $this->_countValidatorClass($this, $limit);
432 25 : $this->_countValidatorClass = 'Mockery\CountValidator\Exact';
433 25 : return $this;
434 : }
435 :
436 : /**
437 : * Indicates that this expectation is never expected to be called
438 : *
439 : * @return self
440 : */
441 : public function never()
442 : {
443 1 : return $this->times(0);
444 : }
445 :
446 : /**
447 : * Indicates that this expectation is expected exactly once
448 : *
449 : * @return self
450 : */
451 : public function once()
452 : {
453 24 : return $this->times(1);
454 : }
455 :
456 : /**
457 : * Indicates that this expectation is expected exactly twice
458 : *
459 : * @return self
460 : */
461 : public function twice()
462 : {
463 0 : return $this->times(2);
464 : }
465 :
466 : /**
467 : * Sets next count validator to the AtLeast instance
468 : *
469 : * @return self
470 : */
471 : public function atLeast()
472 : {
473 0 : $this->_countValidatorClass = 'Mockery\CountValidator\AtLeast';
474 0 : return $this;
475 : }
476 :
477 : /**
478 : * Sets next count validator to the AtMost instance
479 : *
480 : * @return self
481 : */
482 : public function atMost()
483 : {
484 0 : $this->_countValidatorClass = 'Mockery\CountValidator\AtMost';
485 0 : return $this;
486 : }
487 :
488 : /**
489 : * Shorthand for setting minimum and maximum constraints on call counts
490 : *
491 : * @param int $minimum
492 : * @param int $maximum
493 : */
494 : public function between($minimum, $maximum)
495 : {
496 0 : return $this->atLeast()->times($minimum)->atMost()->times($maximum);
497 : }
498 :
499 : /**
500 : * Indicates that this expectation must be called in a specific given order
501 : *
502 : * @param string $group Name of the ordered group
503 : * @return self
504 : */
505 : public function ordered($group = null)
506 : {
507 0 : if ($this->_globally) {
508 0 : $this->_globalOrderNumber = $this->_defineOrdered($group, $this->_mock->mockery_getContainer());
509 0 : } else {
510 0 : $this->_orderNumber = $this->_defineOrdered($group, $this->_mock);
511 : }
512 0 : $this->_globally = false;
513 0 : return $this;
514 : }
515 :
516 : /**
517 : * Indicates call order should apply globally
518 : *
519 : * @return self
520 : */
521 : public function globally()
522 : {
523 0 : $this->_globally = true;
524 0 : return $this;
525 : }
526 :
527 : /**
528 : * Setup the ordering tracking on the mock or mock container
529 : *
530 : * @param string $group
531 : * @param object $ordering
532 : * @return int
533 : */
534 : protected function _defineOrdered($group, $ordering)
535 : {
536 0 : $groups = $ordering->mockery_getGroups();
537 0 : if (is_null($group)) {
538 0 : $result = $ordering->mockery_allocateOrder();
539 0 : } elseif (isset($groups[$group])) {
540 0 : $result = $groups[$group];
541 0 : } else {
542 0 : $result = $ordering->mockery_allocateOrder();
543 0 : $ordering->mockery_setGroup($group, $result);
544 : }
545 0 : return $result;
546 : }
547 :
548 : /**
549 : * Return order number
550 : *
551 : * @return int
552 : */
553 : public function getOrderNumber()
554 : {
555 0 : return $this->_orderNumber;
556 : }
557 :
558 : /**
559 : * Mark this expectation as being a default
560 : *
561 : * @return self
562 : */
563 : public function byDefault()
564 : {
565 0 : $director = $this->_mock->mockery_getExpectationsFor($this->_name);
566 0 : if(!empty($director)) {
567 0 : $director->makeExpectationDefault($this);
568 0 : }
569 0 : return $this;
570 : }
571 :
572 : /**
573 : * Return the parent mock of the expectation
574 : *
575 : * @return \Mockery\MockInterface
576 : */
577 : public function getMock()
578 : {
579 0 : return $this->_mock;
580 : }
581 :
582 : /**
583 : * Cloning logic
584 : *
585 : */
586 : public function __clone()
587 : {
588 0 : $newValidators = array();
589 0 : $countValidators = $this->_countValidators;
590 0 : foreach ($countValidators as $validator) {
591 0 : $newValidators[] = clone $validator;
592 0 : }
593 0 : $this->_countValidators = $newValidators;
594 0 : }
595 :
596 : }
|