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 * Builder for mocked or stubbed invocations.
13 *
14 * Provides methods for building expectations without having to resort to
15 * instantiating the various matchers manually. These methods also form a
16 * more natural way of reading the expectation. This class should be together
17 * with the test case PHPUnit_Framework_MockObject_TestCase.
18 *
19 * @since Class available since Release 1.0.0
20 */
21class PHPUnit_Framework_MockObject_Builder_InvocationMocker implements PHPUnit_Framework_MockObject_Builder_MethodNameMatch
22{
23    /**
24     * @var PHPUnit_Framework_MockObject_Stub_MatcherCollection
25     */
26    protected $collection;
27
28    /**
29     * @var PHPUnit_Framework_MockObject_Matcher
30     */
31    protected $matcher;
32
33    /**
34     * @var string[]
35     */
36    private $configurableMethods = [];
37
38    /**
39     * @param PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection
40     * @param PHPUnit_Framework_MockObject_Matcher_Invocation     $invocationMatcher
41     * @param array                                               $configurableMethods
42     */
43    public function __construct(PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection, PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher, array $configurableMethods)
44    {
45        $this->collection = $collection;
46        $this->matcher    = new PHPUnit_Framework_MockObject_Matcher(
47            $invocationMatcher
48        );
49
50        $this->collection->addMatcher($this->matcher);
51
52        $this->configurableMethods = $configurableMethods;
53    }
54
55    /**
56     * @return PHPUnit_Framework_MockObject_Matcher
57     */
58    public function getMatcher()
59    {
60        return $this->matcher;
61    }
62
63    /**
64     * @param mixed $id
65     *
66     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
67     */
68    public function id($id)
69    {
70        $this->collection->registerId($id, $this);
71
72        return $this;
73    }
74
75    /**
76     * @param PHPUnit_Framework_MockObject_Stub $stub
77     *
78     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
79     */
80    public function will(PHPUnit_Framework_MockObject_Stub $stub)
81    {
82        $this->matcher->stub = $stub;
83
84        return $this;
85    }
86
87    /**
88     * @param mixed $value
89     * @param mixed $nextValues, ...
90     *
91     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
92     */
93    public function willReturn($value, ...$nextValues)
94    {
95        $stub = count($nextValues) === 0 ?
96            new PHPUnit_Framework_MockObject_Stub_Return($value) :
97            new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls(
98               array_merge([$value], $nextValues)
99            );
100
101        return $this->will($stub);
102    }
103
104    /**
105     * @param mixed $reference
106     *
107     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
108     */
109    public function willReturnReference(&$reference)
110    {
111        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnReference($reference);
112
113        return $this->will($stub);
114    }
115
116    /**
117     * @param array $valueMap
118     *
119     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
120     */
121    public function willReturnMap(array $valueMap)
122    {
123        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnValueMap(
124            $valueMap
125        );
126
127        return $this->will($stub);
128    }
129
130    /**
131     * @param mixed $argumentIndex
132     *
133     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
134     */
135    public function willReturnArgument($argumentIndex)
136    {
137        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnArgument(
138            $argumentIndex
139        );
140
141        return $this->will($stub);
142    }
143
144    /**
145     * @param callable $callback
146     *
147     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
148     */
149    public function willReturnCallback($callback)
150    {
151        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnCallback(
152            $callback
153        );
154
155        return $this->will($stub);
156    }
157
158    /**
159     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
160     */
161    public function willReturnSelf()
162    {
163        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnSelf;
164
165        return $this->will($stub);
166    }
167
168    /**
169     * @param mixed $values, ...
170     *
171     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
172     */
173    public function willReturnOnConsecutiveCalls(...$values)
174    {
175        $stub = new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($values);
176
177        return $this->will($stub);
178    }
179
180    /**
181     * @param Exception $exception
182     *
183     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
184     */
185    public function willThrowException(Exception $exception)
186    {
187        $stub = new PHPUnit_Framework_MockObject_Stub_Exception($exception);
188
189        return $this->will($stub);
190    }
191
192    /**
193     * @param mixed $id
194     *
195     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
196     */
197    public function after($id)
198    {
199        $this->matcher->afterMatchBuilderId = $id;
200
201        return $this;
202    }
203
204    /**
205     * Validate that a parameters matcher can be defined, throw exceptions otherwise.
206     *
207     * @throws PHPUnit_Framework_MockObject_RuntimeException
208     */
209    private function canDefineParameters()
210    {
211        if ($this->matcher->methodNameMatcher === null) {
212            throw new PHPUnit_Framework_MockObject_RuntimeException(
213                'Method name matcher is not defined, cannot define parameter ' .
214                'matcher without one'
215            );
216        }
217
218        if ($this->matcher->parametersMatcher !== null) {
219            throw new PHPUnit_Framework_MockObject_RuntimeException(
220                'Parameter matcher is already defined, cannot redefine'
221            );
222        }
223    }
224
225    /**
226     * @param  array ...$arguments
227     *
228     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
229     */
230    public function with(...$arguments)
231    {
232        $this->canDefineParameters();
233
234        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_Parameters($arguments);
235
236        return $this;
237    }
238
239    /**
240     * @param  array ...$arguments
241     *
242     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
243     */
244    public function withConsecutive(...$arguments)
245    {
246        $this->canDefineParameters();
247
248        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters($arguments);
249
250        return $this;
251    }
252
253    /**
254     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
255     */
256    public function withAnyParameters()
257    {
258        $this->canDefineParameters();
259
260        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters;
261
262        return $this;
263    }
264
265    /**
266     * @param PHPUnit_Framework_Constraint|string $constraint
267     *
268     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
269     */
270    public function method($constraint)
271    {
272        if ($this->matcher->methodNameMatcher !== null) {
273            throw new PHPUnit_Framework_MockObject_RuntimeException(
274                'Method name matcher is already defined, cannot redefine'
275            );
276        }
277
278        if (is_string($constraint) && !in_array(strtolower($constraint), $this->configurableMethods)) {
279            throw new PHPUnit_Framework_MockObject_RuntimeException(
280                sprintf(
281                    'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static',
282                    $constraint
283                )
284            );
285        }
286
287        $this->matcher->methodNameMatcher = new PHPUnit_Framework_MockObject_Matcher_MethodName($constraint);
288
289        return $this;
290    }
291}
292