File "Expectation.php"
Full Path: /home/pulsehostuk9/public_html/invoicer.pulsehost.co.uk/vendor/mockery/mockery/library/Mockery/Adapter/Expectation.php
File size: 24.18 KB
MIME-type: text/x-php
Charset: utf-8
<?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;
}
}