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 * Main matcher which defines a full expectation using method, parameter and
13 * invocation matchers.
14 * This matcher encapsulates all the other matchers and allows the builder to
15 * set the specific matchers when the appropriate methods are called (once(),
16 * where() etc.).
17 *
18 * All properties are public so that they can easily be accessed by the builder.
19 *
20 * @since Class available since Release 1.0.0
21 */
22class PHPUnit_Framework_MockObject_Matcher implements PHPUnit_Framework_MockObject_Matcher_Invocation
23{
24    /**
25     * @var PHPUnit_Framework_MockObject_Matcher_Invocation
26     */
27    public $invocationMatcher;
28
29    /**
30     * @var mixed
31     */
32    public $afterMatchBuilderId = null;
33
34    /**
35     * @var bool
36     */
37    public $afterMatchBuilderIsInvoked = false;
38
39    /**
40     * @var PHPUnit_Framework_MockObject_Matcher_MethodName
41     */
42    public $methodNameMatcher = null;
43
44    /**
45     * @var PHPUnit_Framework_MockObject_Matcher_Parameters
46     */
47    public $parametersMatcher = null;
48
49    /**
50     * @var PHPUnit_Framework_MockObject_Stub
51     */
52    public $stub = null;
53
54    /**
55     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher
56     */
57    public function __construct(PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher)
58    {
59        $this->invocationMatcher = $invocationMatcher;
60    }
61
62    /**
63     * @return string
64     */
65    public function toString()
66    {
67        $list = [];
68
69        if ($this->invocationMatcher !== null) {
70            $list[] = $this->invocationMatcher->toString();
71        }
72
73        if ($this->methodNameMatcher !== null) {
74            $list[] = 'where ' . $this->methodNameMatcher->toString();
75        }
76
77        if ($this->parametersMatcher !== null) {
78            $list[] = 'and ' . $this->parametersMatcher->toString();
79        }
80
81        if ($this->afterMatchBuilderId !== null) {
82            $list[] = 'after ' . $this->afterMatchBuilderId;
83        }
84
85        if ($this->stub !== null) {
86            $list[] = 'will ' . $this->stub->toString();
87        }
88
89        return implode(' ', $list);
90    }
91
92    /**
93     * @param PHPUnit_Framework_MockObject_Invocation $invocation
94     *
95     * @return mixed
96     */
97    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
98    {
99        if ($this->invocationMatcher === null) {
100            throw new PHPUnit_Framework_MockObject_RuntimeException(
101                'No invocation matcher is set'
102            );
103        }
104
105        if ($this->methodNameMatcher === null) {
106            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
107        }
108
109        if ($this->afterMatchBuilderId !== null) {
110            $builder = $invocation->object
111                                  ->__phpunit_getInvocationMocker()
112                                  ->lookupId($this->afterMatchBuilderId);
113
114            if (!$builder) {
115                throw new PHPUnit_Framework_MockObject_RuntimeException(
116                    sprintf(
117                        'No builder found for match builder identification <%s>',
118                        $this->afterMatchBuilderId
119                    )
120                );
121            }
122
123            $matcher = $builder->getMatcher();
124
125            if ($matcher && $matcher->invocationMatcher->hasBeenInvoked()) {
126                $this->afterMatchBuilderIsInvoked = true;
127            }
128        }
129
130        $this->invocationMatcher->invoked($invocation);
131
132        try {
133            if ($this->parametersMatcher !== null &&
134                !$this->parametersMatcher->matches($invocation)) {
135                $this->parametersMatcher->verify();
136            }
137        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
138            throw new PHPUnit_Framework_ExpectationFailedException(
139                sprintf(
140                    "Expectation failed for %s when %s\n%s",
141                    $this->methodNameMatcher->toString(),
142                    $this->invocationMatcher->toString(),
143                    $e->getMessage()
144                ),
145                $e->getComparisonFailure()
146            );
147        }
148
149        if ($this->stub) {
150            return $this->stub->invoke($invocation);
151        }
152
153        return $invocation->generateReturnValue();
154    }
155
156    /**
157     * @param PHPUnit_Framework_MockObject_Invocation $invocation
158     *
159     * @return bool
160     */
161    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
162    {
163        if ($this->afterMatchBuilderId !== null) {
164            $builder = $invocation->object
165                                  ->__phpunit_getInvocationMocker()
166                                  ->lookupId($this->afterMatchBuilderId);
167
168            if (!$builder) {
169                throw new PHPUnit_Framework_MockObject_RuntimeException(
170                    sprintf(
171                        'No builder found for match builder identification <%s>',
172                        $this->afterMatchBuilderId
173                    )
174                );
175            }
176
177            $matcher = $builder->getMatcher();
178
179            if (!$matcher) {
180                return false;
181            }
182
183            if (!$matcher->invocationMatcher->hasBeenInvoked()) {
184                return false;
185            }
186        }
187
188        if ($this->invocationMatcher === null) {
189            throw new PHPUnit_Framework_MockObject_RuntimeException(
190                'No invocation matcher is set'
191            );
192        }
193
194        if ($this->methodNameMatcher === null) {
195            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
196        }
197
198        if (!$this->invocationMatcher->matches($invocation)) {
199            return false;
200        }
201
202        try {
203            if (!$this->methodNameMatcher->matches($invocation)) {
204                return false;
205            }
206        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
207            throw new PHPUnit_Framework_ExpectationFailedException(
208                sprintf(
209                    "Expectation failed for %s when %s\n%s",
210                    $this->methodNameMatcher->toString(),
211                    $this->invocationMatcher->toString(),
212                    $e->getMessage()
213                ),
214                $e->getComparisonFailure()
215            );
216        }
217
218        return true;
219    }
220
221    /**
222     * @throws PHPUnit_Framework_MockObject_RuntimeException
223     * @throws PHPUnit_Framework_ExpectationFailedException
224     */
225    public function verify()
226    {
227        if ($this->invocationMatcher === null) {
228            throw new PHPUnit_Framework_MockObject_RuntimeException(
229                'No invocation matcher is set'
230            );
231        }
232
233        if ($this->methodNameMatcher === null) {
234            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
235        }
236
237        try {
238            $this->invocationMatcher->verify();
239
240            if ($this->parametersMatcher === null) {
241                $this->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters;
242            }
243
244            $invocationIsAny   = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
245            $invocationIsNever = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_InvokedCount && $this->invocationMatcher->isNever();
246
247            if (!$invocationIsAny && !$invocationIsNever) {
248                $this->parametersMatcher->verify();
249            }
250        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
251            throw new PHPUnit_Framework_ExpectationFailedException(
252                sprintf(
253                    "Expectation failed for %s when %s.\n%s",
254                    $this->methodNameMatcher->toString(),
255                    $this->invocationMatcher->toString(),
256                    PHPUnit_Framework_TestFailure::exceptionToString($e)
257                )
258            );
259        }
260    }
261
262    /**
263     * @since Method available since Release 1.2.4
264     */
265    public function hasMatchers()
266    {
267        if ($this->invocationMatcher !== null &&
268            !$this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) {
269            return true;
270        }
271
272        return false;
273    }
274}
275