Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
vendor
/
mockery
/
mockery
/
library
/
Mockery
/
Matcher
:
Expectation.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * Mockery (https://docs.mockery.io/) * * @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md * @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License * @link https://github.com/mockery/mockery for the canonical source repository */ namespace Mockery; use Closure; use Hamcrest\Matcher; use Hamcrest_Matcher; use InvalidArgumentException; use Mockery; use Mockery\CountValidator\AtLeast; use Mockery\CountValidator\AtMost; use Mockery\CountValidator\Exact; use Mockery\Matcher\AndAnyOtherArgs; use Mockery\Matcher\AnyArgs; use Mockery\Matcher\ArgumentListMatcher; use Mockery\Matcher\MatcherInterface; use Mockery\Matcher\MultiArgumentClosure; use Mockery\Matcher\NoArgs; use OutOfBoundsException; use PHPUnit\Framework\Constraint\Constraint; use Throwable; use function array_key_exists; use function array_search; use function array_shift; use function array_slice; use function count; use function current; use function func_get_args; use function get_class; use function in_array; use function is_array; use function is_int; use function is_object; use function is_string; use function sprintf; use function trigger_error; use const E_USER_DEPRECATED; class Expectation implements ExpectationInterface { public const ERROR_ZERO_INVOCATION = 'shouldNotReceive(), never(), times(0) chaining additional invocation count methods has been deprecated and will throw an exception in a future version of Mockery'; /** * Actual count of calls to this expectation * * @var int */ protected $_actualCount = 0; /** * Exception message * * @var null|string */ protected $_because = null; /** * Array of closures executed with given arguments to generate a result * to be returned * * @var array */ protected $_closureQueue = []; /** * The count validator class to use * * @var string */ protected $_countValidatorClass = Exact::class; /** * Count validator store * * @var array */ protected $_countValidators = []; /** * Arguments expected by this expectation * * @var array */ protected $_expectedArgs = []; /** * Expected count of calls to this expectation * * @var int */ protected $_expectedCount = -1; /** * Flag indicating whether the order of calling is determined locally or * globally * * @var bool */ protected $_globally = false; /** * Integer representing the call order of this expectation on a global basis * * @var int */ protected $_globalOrderNumber = null; /** * Mock object to which this expectation belongs * * @var LegacyMockInterface */ protected $_mock = null; /** * Method name * * @var string */ protected $_name = null; /** * Integer representing the call order of this expectation * * @var int */ protected $_orderNumber = null; /** * Flag indicating if the return value should be obtained from the original * class method instead of returning predefined values from the return queue * * @var bool */ protected $_passthru = false; /** * Array of return values as a queue for multiple return sequence * * @var array */ protected $_returnQueue = []; /** * Value to return from this expectation * * @var mixed */ protected $_returnValue = null; /** * Array of values to be set when this expectation matches * * @var array */ protected $_setQueue = []; /** * Flag indicating that an exception is expected to be throw (not returned) * * @var bool */ protected $_throw = false; /** * Constructor * * @param string $name */ public function __construct(LegacyMockInterface $mock, $name) { $this->_mock = $mock; $this->_name = $name; $this->withAnyArgs(); } /** * Cloning logic */ public function __clone() { $newValidators = []; $countValidators = $this->_countValidators; foreach ($countValidators as $validator) { $newValidators[] = clone $validator; } $this->_countValidators = $newValidators; } /** * Return a string with the method name and arguments formatted * * @return string */ public function __toString() { return Mockery::formatArgs($this->_name, $this->_expectedArgs); } /** * Set a return value, or sequential queue of return values * * @param mixed ...$args * * @return self */ public function andReturn(...$args) { $this->_returnQueue = $args; return $this; } /** * Sets up a closure to return the nth argument from the expected method call * * @param int $index * * @return self */ public function andReturnArg($index) { if (! is_int($index) || $index < 0) { throw new InvalidArgumentException( 'Invalid argument index supplied. Index must be a non-negative integer.' ); } $closure = static function (...$args) use ($index) { if (array_key_exists($index, $args)) { return $args[$index]; } throw new OutOfBoundsException( 'Cannot return an argument value. No argument exists for the index ' . $index ); }; $this->_closureQueue = [$closure]; return $this; } /** * @return self */ public function andReturnFalse() { return $this->andReturn(false); } /** * Return null. This is merely a language construct for Mock describing. * * @return self */ public function andReturnNull() { return $this->andReturn(null); } /** * Set a return value, or sequential queue of return values * * @param mixed ...$args * * @return self */ public function andReturns(...$args) { return $this->andReturn(...$args); } /** * Return this mock, like a fluent interface * * @return self */ public function andReturnSelf() { return $this->andReturn($this->_mock); } /** * @return self */ public function andReturnTrue() { return $this->andReturn(true); } /** * Return a self-returning black hole object. * * @return self */ public function andReturnUndefined() { return $this->andReturn(new Undefined()); } /** * Set a closure or sequence of closures with which to generate return * values. The arguments passed to the expected method are passed to the * closures as parameters. * * @param callable ...$args * * @return self */ public function andReturnUsing(...$args) { $this->_closureQueue = $args; return $this; } /** * Set a sequential queue of return values with an array * * @return self */ public function andReturnValues(array $values) { return $this->andReturn(...$values); } /** * Register values to be set to a public property each time this expectation occurs * * @param string $name * @param array ...$values * * @return self */ public function andSet($name, ...$values) { $this->_setQueue[$name] = $values; return $this; } /** * Set Exception class and arguments to that class to be thrown * * @param string|Throwable $exception * @param string $message * @param int $code * * @return self */ public function andThrow($exception, $message = '', $code = 0, ?\Exception $previous = null) { $this->_throw = true; if (is_object($exception)) { return $this->andReturn($exception); } return $this->andReturn(new $exception($message, $code, $previous)); } /** * Set Exception classes to be thrown * * @return self */ public function andThrowExceptions(array $exceptions) { $this->_throw = true; foreach ($exceptions as $exception) { if (! is_object($exception)) { throw new Exception('You must pass an array of exception objects to andThrowExceptions'); } } return $this->andReturnValues($exceptions); } public function andThrows($exception, $message = '', $code = 0, ?\Exception $previous = null) { return $this->andThrow($exception, $message, $code, $previous); } /** * Sets up a closure that will yield each of the provided args * * @param mixed ...$args * * @return self */ public function andYield(...$args) { $closure = static function () use ($args) { foreach ($args as $arg) { yield $arg; } }; $this->_closureQueue = [$closure]; return $this; } /** * Sets next count validator to the AtLeast instance * * @return self */ public function atLeast() { $this->_countValidatorClass = AtLeast::class; return $this; } /** * Sets next count validator to the AtMost instance * * @return self */ public function atMost() { $this->_countValidatorClass = AtMost::class; return $this; } /** * Set the exception message * * @param string $message * * @return $this */ public function because($message) { $this->_because = $message; return $this; } /** * Shorthand for setting minimum and maximum constraints on call counts * * @param int $minimum * @param int $maximum */ public function between($minimum, $maximum) { return $this->atLeast()->times($minimum)->atMost()->times($maximum); } /** * Mark this expectation as being a default * * @return self */ public function byDefault() { $director = $this->_mock->mockery_getExpectationsFor($this->_name); if ($director instanceof ExpectationDirector) { $director->makeExpectationDefault($this); } return $this; } /** * @return null|string */ public function getExceptionMessage() { return $this->_because; } /** * Return the parent mock of the expectation * * @return LegacyMockInterface|MockInterface */ public function getMock() { return $this->_mock; } public function getName() { return $this->_name; } /** * Return order number * * @return int */ public function getOrderNumber() { return $this->_orderNumber; } /** * Indicates call order should apply globally * * @return self */ public function globally() { $this->_globally = true; return $this; } /** * Check if there is a constraint on call count * * @return bool */ public function isCallCountConstrained() { return $this->_countValidators !== []; } /** * Checks if this expectation is eligible for additional calls * * @return bool */ public function isEligible() { foreach ($this->_countValidators as $validator) { if (! $validator->isEligible($this->_actualCount)) { return false; } } return true; } /** * Check if passed arguments match an argument expectation * * @return bool */ public function matchArgs(array $args) { if ($this->isArgumentListMatcher()) { return $this->_matchArg($this->_expectedArgs[0], $args); } $argCount = count($args); $expectedArgsCount = count($this->_expectedArgs); if ($argCount === $expectedArgsCount) { return $this->_matchArgs($args); } $lastExpectedArgument = $this->_expectedArgs[$expectedArgsCount - 1]; if ($lastExpectedArgument instanceof AndAnyOtherArgs) { $firstCorrespondingKey = array_search($lastExpectedArgument, $this->_expectedArgs, true); $args = array_slice($args, 0, $firstCorrespondingKey); return $this->_matchArgs($args); } return false; } /** * Indicates that this expectation is never expected to be called * * @return self */ public function never() { return $this->times(0); } /** * Indicates that this expectation is expected exactly once * * @return self */ public function once() { return $this->times(1); } /** * Indicates that this expectation must be called in a specific given order * * @param string $group Name of the ordered group * * @return self */ public function ordered($group = null) { if ($this->_globally) { $this->_globalOrderNumber = $this->_defineOrdered($group, $this->_mock->mockery_getContainer()); } else { $this->_orderNumber = $this->_defineOrdered($group, $this->_mock); } $this->_globally = false; return $this; } /** * Flag this expectation as calling the original class method with * the provided arguments instead of using a return value queue. * * @return self */ public function passthru() { if ($this->_mock instanceof Mock) { throw new Exception( 'Mock Objects not created from a loaded/existing class are incapable of passing method calls through to a parent class' ); } $this->_passthru = true; return $this; } /** * Alias to andSet(). Allows the natural English construct * - set('foo', 'bar')->andReturn('bar') * * @param string $name * @param mixed $value * * @return self */ public function set($name, $value) { return $this->andSet(...func_get_args()); } /** * Indicates the number of times this expectation should occur * * @param int $limit * * @throws InvalidArgumentException * * @return self */ public function times($limit = null) { if ($limit === null) { return $this; } if (! is_int($limit)) { throw new InvalidArgumentException('The passed Times limit should be an integer value'); } if ($this->_expectedCount === 0) { @trigger_error(self::ERROR_ZERO_INVOCATION, E_USER_DEPRECATED); // throw new \InvalidArgumentException(self::ERROR_ZERO_INVOCATION); } if ($limit === 0) { $this->_countValidators = []; } $this->_expectedCount = $limit; $this->_countValidators[$this->_countValidatorClass] = new $this->_countValidatorClass($this, $limit); if ($this->_countValidatorClass !== Exact::class) { $this->_countValidatorClass = Exact::class; unset($this->_countValidators[$this->_countValidatorClass]); } return $this; } /** * Indicates that this expectation is expected exactly twice * * @return self */ public function twice() { return $this->times(2); } /** * Verify call order * * @return void */ public function validateOrder() { if ($this->_orderNumber) { $this->_mock->mockery_validateOrder((string) $this, $this->_orderNumber, $this->_mock); } if ($this->_globalOrderNumber) { $this->_mock->mockery_getContainer()->mockery_validateOrder( (string) $this, $this->_globalOrderNumber, $this->_mock ); } } /** * Verify this expectation * * @return void */ public function verify() { foreach ($this->_countValidators as $validator) { $validator->validate($this->_actualCount); } } /** * Verify the current call, i.e. that the given arguments match those * of this expectation * * @throws Throwable * * @return mixed */ public function verifyCall(array $args) { $this->validateOrder(); ++$this->_actualCount; if ($this->_passthru === true) { return $this->_mock->mockery_callSubjectMethod($this->_name, $args); } $return = $this->_getReturnValue($args); $this->throwAsNecessary($return); $this->_setValues(); return $return; } /** * Expected argument setter for the expectation * * @param mixed ...$args * * @return self */ public function with(...$args) { return $this->withArgs($args); } /** * Set expectation that any arguments are acceptable * * @return self */ public function withAnyArgs() { $this->_expectedArgs = [new AnyArgs()]; return $this; } /** * Expected arguments for the expectation passed as an array or a closure that matches each passed argument on * each function call. * * @param array|Closure $argsOrClosure * * @return self */ public function withArgs($argsOrClosure) { if (is_array($argsOrClosure)) { return $this->withArgsInArray($argsOrClosure); } if ($argsOrClosure instanceof Closure) { return $this->withArgsMatchedByClosure($argsOrClosure); } throw new InvalidArgumentException(sprintf( 'Call to %s with an invalid argument (%s), only array and closure are allowed', __METHOD__, $argsOrClosure )); } /** * Set with() as no arguments expected * * @return self */ public function withNoArgs() { $this->_expectedArgs = [new NoArgs()]; return $this; } /** * Expected arguments should partially match the real arguments * * @param mixed ...$expectedArgs * * @return self */ public function withSomeOfArgs(...$expectedArgs) { return $this->withArgs(static function (...$args) use ($expectedArgs): bool { foreach ($expectedArgs as $expectedArg) { if (! in_array($expectedArg, $args, true)) { return false; } } return true; }); } /** * Indicates this expectation should occur zero or more times * * @return self */ public function zeroOrMoreTimes() { return $this->atLeast()->never(); } /** * Setup the ordering tracking on the mock or mock container * * @param string $group * @param object $ordering * * @return int */ protected function _defineOrdered($group, $ordering) { $groups = $ordering->mockery_getGroups(); if ($group === null) { return $ordering->mockery_allocateOrder(); } if (array_key_exists($group, $groups)) { return $groups[$group]; } $result = $ordering->mockery_allocateOrder(); $ordering->mockery_setGroup($group, $result); return $result; } /** * Fetch the return value for the matching args * * @return mixed */ protected function _getReturnValue(array $args) { $closureQueueCount = count($this->_closureQueue); if ($closureQueueCount > 1) { return array_shift($this->_closureQueue)(...$args); } if ($closureQueueCount > 0) { return current($this->_closureQueue)(...$args); } $returnQueueCount = count($this->_returnQueue); if ($returnQueueCount > 1) { return array_shift($this->_returnQueue); } if ($returnQueueCount > 0) { return current($this->_returnQueue); } return $this->_mock->mockery_returnValueForMethod($this->_name); } /** * Check if passed argument matches an argument expectation * * @param mixed $expected * @param mixed $actual * * @return bool */ protected function _matchArg($expected, &$actual) { if ($expected === $actual) { return true; } if ($expected instanceof MatcherInterface) { return $expected->match($actual); } if ($expected instanceof Constraint) { return (bool) $expected->evaluate($actual, '', true); } if ($expected instanceof Matcher || $expected instanceof Hamcrest_Matcher) { @trigger_error('Hamcrest package has been deprecated and will be removed in 2.0', E_USER_DEPRECATED); return $expected->matches($actual); } if (is_object($expected)) { $matcher = Mockery::getConfiguration()->getDefaultMatcher(get_class($expected)); return $matcher === null ? false : $this->_matchArg(new $matcher($expected), $actual); } if (is_object($actual) && is_string($expected) && $actual instanceof $expected) { return true; } return $expected == $actual; } /** * Check if the passed arguments match the expectations, one by one. * * @param array $args * * @return bool */ protected function _matchArgs($args) { for ($index = 0, $argCount = count($args); $index < $argCount; ++$index) { $param = &$args[$index]; if (! $this->_matchArg($this->_expectedArgs[$index], $param)) { return false; } } return true; } /** * Sets public properties with queued values to the mock object * * @return void */ protected function _setValues() { $mockClass = get_class($this->_mock); $container = $this->_mock->mockery_getContainer(); $mocks = $container->getMocks(); foreach ($this->_setQueue as $name => &$values) { if ($values === []) { continue; } $value = array_shift($values); $this->_mock->{$name} = $value; foreach ($mocks as $mock) { if (! $mock instanceof $mockClass) { continue; } if (! $mock->mockery_isInstance()) { continue; } $mock->{$name} = $value; } } } /** * @template TExpectedArg * * @param TExpectedArg $expectedArg * * @return bool */ private function isAndAnyOtherArgumentsMatcher($expectedArg) { return $expectedArg instanceof AndAnyOtherArgs; } /** * Check if the registered expectation is an ArgumentListMatcher * * @return bool */ private function isArgumentListMatcher() { return $this->_expectedArgs !== [] && $this->_expectedArgs[0] instanceof ArgumentListMatcher; } /** * Throws an exception if the expectation has been configured to do so * * @param Throwable $return * * @throws Throwable * * @return void */ private function throwAsNecessary($return) { if (! $this->_throw) { return; } if (! $return instanceof Throwable) { return; } throw $return; } /** * Expected arguments for the expectation passed as an array * * @return self */ private function withArgsInArray(array $arguments) { if ($arguments === []) { return $this->withNoArgs(); } $this->_expectedArgs = $arguments; return $this; } /** * Expected arguments have to be matched by the given closure. * * @return self */ private function withArgsMatchedByClosure(Closure $closure) { $this->_expectedArgs = [new MultiArgumentClosure($closure)]; return $this; } }