1<?php
2/*
3 * This file is part of the PHPUnit_MockObject package.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * Invocation matcher which looks for specific parameters in the invocations.
13 *
14 * Checks the parameters of all incoming invocations, the parameter list is
15 * checked against the defined constraints in $parameters. If the constraint
16 * is met it will return true in matches().
17 *
18 * @since Class available since Release 1.0.0
19 */
20class PHPUnit_Framework_MockObject_Matcher_Parameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation
21{
22    /**
23     * @var PHPUnit_Framework_Constraint[]
24     */
25    protected $parameters = [];
26
27    /**
28     * @var PHPUnit_Framework_MockObject_Invocation
29     */
30    protected $invocation;
31
32    /**
33     * @var PHPUnit_Framework_ExpectationFailedException
34     */
35    private $parameterVerificationResult;
36
37    /**
38     * @param array $parameters
39     */
40    public function __construct(array $parameters)
41    {
42        foreach ($parameters as $parameter) {
43            if (!($parameter instanceof PHPUnit_Framework_Constraint)) {
44                $parameter = new PHPUnit_Framework_Constraint_IsEqual(
45                    $parameter
46                );
47            }
48
49            $this->parameters[] = $parameter;
50        }
51    }
52
53    /**
54     * @return string
55     */
56    public function toString()
57    {
58        $text = 'with parameter';
59
60        foreach ($this->parameters as $index => $parameter) {
61            if ($index > 0) {
62                $text .= ' and';
63            }
64
65            $text .= ' ' . $index . ' ' . $parameter->toString();
66        }
67
68        return $text;
69    }
70
71    /**
72     * @param PHPUnit_Framework_MockObject_Invocation $invocation
73     *
74     * @return bool
75     */
76    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
77    {
78        $this->invocation                  = $invocation;
79        $this->parameterVerificationResult = null;
80
81        try {
82            $this->parameterVerificationResult = $this->verify();
83
84            return $this->parameterVerificationResult;
85        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
86            $this->parameterVerificationResult = $e;
87
88            throw $this->parameterVerificationResult;
89        }
90    }
91
92    /**
93     * Checks if the invocation $invocation matches the current rules. If it
94     * does the matcher will get the invoked() method called which should check
95     * if an expectation is met.
96     *
97     * @return bool
98     *
99     * @throws PHPUnit_Framework_ExpectationFailedException
100     */
101    public function verify()
102    {
103        if (isset($this->parameterVerificationResult)) {
104            return $this->guardAgainstDuplicateEvaluationOfParameterConstraints();
105        }
106
107        if ($this->invocation === null) {
108            throw new PHPUnit_Framework_ExpectationFailedException(
109                'Mocked method does not exist.'
110            );
111        }
112
113        if (count($this->invocation->parameters) < count($this->parameters)) {
114            $message = 'Parameter count for invocation %s is too low.';
115
116            // The user called `->with($this->anything())`, but may have meant
117            // `->withAnyParameters()`.
118            //
119            // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199
120            if (count($this->parameters) === 1 &&
121                get_class($this->parameters[0]) === 'PHPUnit_Framework_Constraint_IsAnything') {
122                $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead.";
123            }
124
125            throw new PHPUnit_Framework_ExpectationFailedException(
126                sprintf($message, $this->invocation->toString())
127            );
128        }
129
130        foreach ($this->parameters as $i => $parameter) {
131            $parameter->evaluate(
132                $this->invocation->parameters[$i],
133                sprintf(
134                    'Parameter %s for invocation %s does not match expected ' .
135                    'value.',
136                    $i,
137                    $this->invocation->toString()
138                )
139            );
140        }
141
142        return true;
143    }
144
145    /**
146     * @return bool
147     *
148     * @throws PHPUnit_Framework_ExpectationFailedException
149     */
150    private function guardAgainstDuplicateEvaluationOfParameterConstraints()
151    {
152        if ($this->parameterVerificationResult instanceof Exception) {
153            throw $this->parameterVerificationResult;
154        }
155
156        return (bool) $this->parameterVerificationResult;
157    }
158}
159