Pull merge.
[yaffs-website] / vendor / phpspec / prophecy / src / Prophecy / Call / CallCenter.php
1 <?php
2
3 /*
4  * This file is part of the Prophecy.
5  * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6  *     Marcello Duarte <marcello.duarte@gmail.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Prophecy\Call;
13
14 use Prophecy\Exception\Prophecy\MethodProphecyException;
15 use Prophecy\Prophecy\MethodProphecy;
16 use Prophecy\Prophecy\ObjectProphecy;
17 use Prophecy\Argument\ArgumentsWildcard;
18 use Prophecy\Util\StringUtil;
19 use Prophecy\Exception\Call\UnexpectedCallException;
20
21 /**
22  * Calls receiver & manager.
23  *
24  * @author Konstantin Kudryashov <ever.zet@gmail.com>
25  */
26 class CallCenter
27 {
28     private $util;
29
30     /**
31      * @var Call[]
32      */
33     private $recordedCalls = array();
34
35     /**
36      * Initializes call center.
37      *
38      * @param StringUtil $util
39      */
40     public function __construct(StringUtil $util = null)
41     {
42         $this->util = $util ?: new StringUtil;
43     }
44
45     /**
46      * Makes and records specific method call for object prophecy.
47      *
48      * @param ObjectProphecy $prophecy
49      * @param string         $methodName
50      * @param array          $arguments
51      *
52      * @return mixed Returns null if no promise for prophecy found or promise return value.
53      *
54      * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found
55      */
56     public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments)
57     {
58         // For efficiency exclude 'args' from the generated backtrace
59         if (PHP_VERSION_ID >= 50400) {
60             // Limit backtrace to last 3 calls as we don't use the rest
61             // Limit argument was introduced in PHP 5.4.0
62             $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
63         } elseif (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
64             // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6
65             $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
66         } else {
67             $backtrace = debug_backtrace();
68         }
69
70         $file = $line = null;
71         if (isset($backtrace[2]) && isset($backtrace[2]['file'])) {
72             $file = $backtrace[2]['file'];
73             $line = $backtrace[2]['line'];
74         }
75
76         // If no method prophecies defined, then it's a dummy, so we'll just return null
77         if ('__destruct' === $methodName || 0 == count($prophecy->getMethodProphecies())) {
78             $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
79
80             return null;
81         }
82
83         // There are method prophecies, so it's a fake/stub. Searching prophecy for this call
84         $matches = array();
85         foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
86             if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
87                 $matches[] = array($score, $methodProphecy);
88             }
89         }
90
91         // If fake/stub doesn't have method prophecy for this call - throw exception
92         if (!count($matches)) {
93             throw $this->createUnexpectedCallException($prophecy, $methodName, $arguments);
94         }
95
96         // Sort matches by their score value
97         @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; });
98
99         $score = $matches[0][0];
100         // If Highest rated method prophecy has a promise - execute it or return null instead
101         $methodProphecy = $matches[0][1];
102         $returnValue = null;
103         $exception   = null;
104         if ($promise = $methodProphecy->getPromise()) {
105             try {
106                 $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy);
107             } catch (\Exception $e) {
108                 $exception = $e;
109             }
110         }
111
112         if ($methodProphecy->hasReturnVoid() && $returnValue !== null) {
113             throw new MethodProphecyException(
114                 "The method \"$methodName\" has a void return type, but the promise returned a value",
115                 $methodProphecy
116             );
117         }
118
119         $this->recordedCalls[] = $call = new Call(
120             $methodName, $arguments, $returnValue, $exception, $file, $line
121         );
122         $call->addScore($methodProphecy->getArgumentsWildcard(), $score);
123
124         if (null !== $exception) {
125             throw $exception;
126         }
127
128         return $returnValue;
129     }
130
131     /**
132      * Searches for calls by method name & arguments wildcard.
133      *
134      * @param string            $methodName
135      * @param ArgumentsWildcard $wildcard
136      *
137      * @return Call[]
138      */
139     public function findCalls($methodName, ArgumentsWildcard $wildcard)
140     {
141         return array_values(
142             array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
143                 return $methodName === $call->getMethodName()
144                     && 0 < $call->getScore($wildcard)
145                 ;
146             })
147         );
148     }
149
150     private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
151                                                    array $arguments)
152     {
153         $classname = get_class($prophecy->reveal());
154         $indentationLength = 8; // looks good
155         $argstring = implode(
156             ",\n",
157             $this->indentArguments(
158                 array_map(array($this->util, 'stringify'), $arguments),
159                 $indentationLength
160             )
161         );
162
163         $expected = array();
164
165         foreach (call_user_func_array('array_merge', $prophecy->getMethodProphecies()) as $methodProphecy) {
166             $expected[] = sprintf(
167                 "  - %s(\n" .
168                 "%s\n" .
169                 "    )",
170                 $methodProphecy->getMethodName(),
171                 implode(
172                     ",\n",
173                     $this->indentArguments(
174                         array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()),
175                         $indentationLength
176                     )
177                 )
178             );
179         }
180
181         return new UnexpectedCallException(
182             sprintf(
183                 "Unexpected method call on %s:\n".
184                 "  - %s(\n".
185                 "%s\n".
186                 "    )\n".
187                 "expected calls were:\n".
188                 "%s",
189
190                 $classname, $methodName, $argstring, implode("\n", $expected)
191             ),
192             $prophecy, $methodName, $arguments
193
194         );
195     }
196
197     private function formatExceptionMessage(MethodProphecy $methodProphecy)
198     {
199         return sprintf(
200             "  - %s(\n".
201             "%s\n".
202             "    )",
203             $methodProphecy->getMethodName(),
204             implode(
205                 ",\n",
206                 $this->indentArguments(
207                     array_map(
208                         function ($token) {
209                             return (string) $token;
210                         },
211                         $methodProphecy->getArgumentsWildcard()->getTokens()
212                     ),
213                     $indentationLength
214                 )
215             )
216         );
217     }
218
219     private function indentArguments(array $arguments, $indentationLength)
220     {
221         return preg_replace_callback(
222             '/^/m',
223             function () use ($indentationLength) {
224                 return str_repeat(' ', $indentationLength);
225             },
226             $arguments
227         );
228     }
229 }