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