Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: namespace PHPUnit\Framework\MockObject\Matcher; Chris@14: Chris@14: use PHPUnit\Framework\Constraint\Constraint; Chris@14: use PHPUnit\Framework\Constraint\IsAnything; Chris@14: use PHPUnit\Framework\Constraint\IsEqual; Chris@14: use PHPUnit\Framework\ExpectationFailedException; Chris@14: use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; Chris@14: Chris@14: /** Chris@14: * Invocation matcher which looks for specific parameters in the invocations. Chris@14: * Chris@14: * Checks the parameters of all incoming invocations, the parameter list is Chris@14: * checked against the defined constraints in $parameters. If the constraint Chris@14: * is met it will return true in matches(). Chris@14: */ Chris@14: class Parameters extends StatelessInvocation Chris@14: { Chris@14: /** Chris@14: * @var Constraint[] Chris@14: */ Chris@14: private $parameters = []; Chris@14: Chris@14: /** Chris@14: * @var BaseInvocation Chris@14: */ Chris@14: private $invocation; Chris@14: Chris@14: /** Chris@14: * @var ExpectationFailedException Chris@14: */ Chris@14: private $parameterVerificationResult; Chris@14: Chris@14: /** Chris@14: * @param array $parameters Chris@14: * Chris@14: * @throws \PHPUnit\Framework\Exception Chris@14: */ Chris@14: public function __construct(array $parameters) Chris@14: { Chris@14: foreach ($parameters as $parameter) { Chris@14: if (!($parameter instanceof Constraint)) { Chris@14: $parameter = new IsEqual( Chris@14: $parameter Chris@14: ); Chris@14: } Chris@14: Chris@14: $this->parameters[] = $parameter; Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * @return string Chris@14: */ Chris@14: public function toString() Chris@14: { Chris@14: $text = 'with parameter'; Chris@14: Chris@14: foreach ($this->parameters as $index => $parameter) { Chris@14: if ($index > 0) { Chris@14: $text .= ' and'; Chris@14: } Chris@14: Chris@14: $text .= ' ' . $index . ' ' . $parameter->toString(); Chris@14: } Chris@14: Chris@14: return $text; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param BaseInvocation $invocation Chris@14: * Chris@14: * @return bool Chris@14: * Chris@14: * @throws \Exception Chris@14: */ Chris@14: public function matches(BaseInvocation $invocation) Chris@14: { Chris@14: $this->invocation = $invocation; Chris@14: $this->parameterVerificationResult = null; Chris@14: Chris@14: try { Chris@14: $this->parameterVerificationResult = $this->verify(); Chris@14: Chris@14: return $this->parameterVerificationResult; Chris@14: } catch (ExpectationFailedException $e) { Chris@14: $this->parameterVerificationResult = $e; Chris@14: Chris@14: throw $this->parameterVerificationResult; Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Checks if the invocation $invocation matches the current rules. If it Chris@14: * does the matcher will get the invoked() method called which should check Chris@14: * if an expectation is met. Chris@14: * Chris@14: * @return bool Chris@14: * Chris@14: * @throws ExpectationFailedException Chris@14: */ Chris@14: public function verify() Chris@14: { Chris@14: if (isset($this->parameterVerificationResult)) { Chris@14: return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); Chris@14: } Chris@14: Chris@14: if ($this->invocation === null) { Chris@14: throw new ExpectationFailedException('Mocked method does not exist.'); Chris@14: } Chris@14: Chris@14: if (\count($this->invocation->getParameters()) < \count($this->parameters)) { Chris@14: $message = 'Parameter count for invocation %s is too low.'; Chris@14: Chris@14: // The user called `->with($this->anything())`, but may have meant Chris@14: // `->withAnyParameters()`. Chris@14: // Chris@14: // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 Chris@14: if (\count($this->parameters) === 1 && Chris@14: \get_class($this->parameters[0]) === IsAnything::class) { Chris@14: $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; Chris@14: } Chris@14: Chris@14: throw new ExpectationFailedException( Chris@14: \sprintf($message, $this->invocation->toString()) Chris@14: ); Chris@14: } Chris@14: Chris@14: foreach ($this->parameters as $i => $parameter) { Chris@14: $parameter->evaluate( Chris@14: $this->invocation->getParameters()[$i], Chris@14: \sprintf( Chris@14: 'Parameter %s for invocation %s does not match expected ' . Chris@14: 'value.', Chris@14: $i, Chris@14: $this->invocation->toString() Chris@14: ) Chris@14: ); Chris@14: } Chris@14: Chris@14: return true; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @return bool Chris@14: * Chris@14: * @throws ExpectationFailedException Chris@14: */ Chris@14: private function guardAgainstDuplicateEvaluationOfParameterConstraints() Chris@14: { Chris@14: if ($this->parameterVerificationResult instanceof \Exception) { Chris@14: throw $this->parameterVerificationResult; Chris@14: } Chris@14: Chris@14: return (bool) $this->parameterVerificationResult; Chris@14: } Chris@14: }