4 * This file is part of the Prophecy.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6 * Marcello Duarte <marcello.duarte@gmail.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Prophecy\Call;
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;
22 * Calls receiver & manager.
24 * @author Konstantin Kudryashov <ever.zet@gmail.com>
33 private $recordedCalls = array();
36 * Initializes call center.
38 * @param StringUtil $util
40 public function __construct(StringUtil $util = null)
42 $this->util = $util ?: new StringUtil;
46 * Makes and records specific method call for object prophecy.
48 * @param ObjectProphecy $prophecy
49 * @param string $methodName
50 * @param array $arguments
52 * @return mixed Returns null if no promise for prophecy found or promise return value.
54 * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found
56 public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments)
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);
67 $backtrace = debug_backtrace();
71 if (isset($backtrace[2]) && isset($backtrace[2]['file'])) {
72 $file = $backtrace[2]['file'];
73 $line = $backtrace[2]['line'];
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);
83 // There are method prophecies, so it's a fake/stub. Searching prophecy for this call
85 foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
86 if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
87 $matches[] = array($score, $methodProphecy);
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);
96 // Sort matches by their score value
97 @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; });
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];
104 if ($promise = $methodProphecy->getPromise()) {
106 $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy);
107 } catch (\Exception $e) {
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",
119 $this->recordedCalls[] = $call = new Call(
120 $methodName, $arguments, $returnValue, $exception, $file, $line
122 $call->addScore($methodProphecy->getArgumentsWildcard(), $score);
124 if (null !== $exception) {
132 * Searches for calls by method name & arguments wildcard.
134 * @param string $methodName
135 * @param ArgumentsWildcard $wildcard
139 public function findCalls($methodName, ArgumentsWildcard $wildcard)
142 array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
143 return $methodName === $call->getMethodName()
144 && 0 < $call->getScore($wildcard)
150 private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
153 $classname = get_class($prophecy->reveal());
154 $indentationLength = 8; // looks good
155 $argstring = implode(
157 $this->indentArguments(
158 array_map(array($this->util, 'stringify'), $arguments),
165 foreach (call_user_func_array('array_merge', $prophecy->getMethodProphecies()) as $methodProphecy) {
166 $expected[] = sprintf(
170 $methodProphecy->getMethodName(),
173 $this->indentArguments(
174 array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()),
181 return new UnexpectedCallException(
183 "Unexpected method call on %s:\n".
187 "expected calls were:\n".
190 $classname, $methodName, $argstring, implode("\n", $expected)
192 $prophecy, $methodName, $arguments
197 private function formatExceptionMessage(MethodProphecy $methodProphecy)
203 $methodProphecy->getMethodName(),
206 $this->indentArguments(
209 return (string) $token;
211 $methodProphecy->getArgumentsWildcard()->getTokens()
219 private function indentArguments(array $arguments, $indentationLength)
221 return preg_replace_callback(
223 function () use ($indentationLength) {
224 return str_repeat(' ', $indentationLength);