Completed
Pull Request — master (#441)
by Geza
01:32
created

CallCenter::makeCall()   C

Complexity

Conditions 13
Paths 66

Size

Total Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 72
rs 5.9042
c 0
b 0
f 0
cc 13
nc 66
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Prophecy.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *     Marcello Duarte <[email protected]>
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
use SplObjectStorage;
21
22
/**
23
 * Calls receiver & manager.
24
 *
25
 * @author Konstantin Kudryashov <[email protected]>
26
 */
27
class CallCenter
28
{
29
    private $util;
30
31
    /**
32
     * @var Call[]
33
     */
34
    private $recordedCalls = array();
35
36
    /**
37
     * @var SplObjectStorage
38
     */
39
    private $unexpectedCalls;
40
41
    /**
42
     * Initializes call center.
43
     *
44
     * @param StringUtil $util
45
     */
46
    public function __construct(StringUtil $util = null)
47
    {
48
        $this->util = $util ?: new StringUtil;
49
        $this->unexpectedCalls = new SplObjectStorage();
50
    }
51
52
    /**
53
     * Makes and records specific method call for object prophecy.
54
     *
55
     * @param ObjectProphecy $prophecy
56
     * @param string         $methodName
57
     * @param array          $arguments
58
     *
59
     * @return mixed Returns null if no promise for prophecy found or promise return value.
60
     *
61
     * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found
62
     */
63
    public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments)
64
    {
65
        // For efficiency exclude 'args' from the generated backtrace
66
        if (PHP_VERSION_ID >= 50400) {
67
            // Limit backtrace to last 3 calls as we don't use the rest
68
            // Limit argument was introduced in PHP 5.4.0
69
            $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
70
        } elseif (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
71
            // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6
72
            $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
73
        } else {
74
            $backtrace = debug_backtrace();
75
        }
76
77
        $file = $line = null;
78
        if (isset($backtrace[2]) && isset($backtrace[2]['file'])) {
79
            $file = $backtrace[2]['file'];
80
            $line = $backtrace[2]['line'];
81
        }
82
83
        // If no method prophecies defined, then it's a dummy, so we'll just return null
84
        if ('__destruct' === $methodName || 0 == count($prophecy->getMethodProphecies())) {
85
            $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
86
87
            return null;
88
        }
89
90
        // There are method prophecies, so it's a fake/stub. Searching prophecy for this call
91
        $matches = $this->findMethodProphecies($prophecy, $methodName, $arguments);
92
93
        // If fake/stub doesn't have method prophecy for this call - throw exception
94
        if (!count($matches)) {
95
            $this->unexpectedCalls->attach(new Call($methodName, $arguments, null, null, $file, $line), $prophecy);
96
            $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
97
98
            return null;
99
        }
100
101
        // Sort matches by their score value
102
        @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; });
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
103
104
        $score = $matches[0][0];
105
        // If Highest rated method prophecy has a promise - execute it or return null instead
106
        $methodProphecy = $matches[0][1];
107
        $returnValue = null;
108
        $exception   = null;
109
        if ($promise = $methodProphecy->getPromise()) {
110
            try {
111
                $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy);
112
            } catch (\Exception $e) {
113
                $exception = $e;
114
            }
115
        }
116
117
        if ($methodProphecy->hasReturnVoid() && $returnValue !== null) {
118
            throw new MethodProphecyException(
119
                "The method \"$methodName\" has a void return type, but the promise returned a value",
120
                $methodProphecy
121
            );
122
        }
123
124
        $this->recordedCalls[] = $call = new Call(
125
            $methodName, $arguments, $returnValue, $exception, $file, $line
126
        );
127
        $call->addScore($methodProphecy->getArgumentsWildcard(), $score);
128
129
        if (null !== $exception) {
130
            throw $exception;
131
        }
132
133
        return $returnValue;
134
    }
135
136
    /**
137
     * Searches for calls by method name & arguments wildcard.
138
     *
139
     * @param string            $methodName
140
     * @param ArgumentsWildcard $wildcard
141
     *
142
     * @return Call[]
143
     */
144
    public function findCalls($methodName, ArgumentsWildcard $wildcard)
145
    {
146
        return array_values(
147
            array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
148
                return $methodName === $call->getMethodName()
149
                    && 0 < $call->getScore($wildcard)
150
                ;
151
            })
152
        );
153
    }
154
155
    /**
156
     * @throws UnexpectedCallException
157
     */
158
    public function checkUnexpectedCalls()
159
    {
160
        /** @var Call $call */
161
        foreach ($this->unexpectedCalls as $call) {
162
            $prophecy = $this->unexpectedCalls[$call];
163
164
            // If fake/stub doesn't have method prophecy for this call - throw exception
165
            if (!count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) {
166
                throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments());
167
            }
168
        }
169
    }
170
171
    private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
172
                                                   array $arguments)
173
    {
174
        $classname = get_class($prophecy->reveal());
175
        $indentationLength = 8; // looks good
176
        $argstring = implode(
177
            ",\n",
178
            $this->indentArguments(
179
                array_map(array($this->util, 'stringify'), $arguments),
180
                $indentationLength
181
            )
182
        );
183
184
        $expected = array();
185
186
        foreach (call_user_func_array('array_merge', $prophecy->getMethodProphecies()) as $methodProphecy) {
187
            $expected[] = sprintf(
188
                "  - %s(\n" .
189
                "%s\n" .
190
                "    )",
191
                $methodProphecy->getMethodName(),
192
                implode(
193
                    ",\n",
194
                    $this->indentArguments(
195
                        array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()),
196
                        $indentationLength
197
                    )
198
                )
199
            );
200
        }
201
202
        return new UnexpectedCallException(
203
            sprintf(
204
                "Unexpected method call on %s:\n".
205
                "  - %s(\n".
206
                "%s\n".
207
                "    )\n".
208
                "expected calls were:\n".
209
                "%s",
210
211
                $classname, $methodName, $argstring, implode("\n", $expected)
212
            ),
213
            $prophecy, $methodName, $arguments
214
215
        );
216
    }
217
218
    private function formatExceptionMessage(MethodProphecy $methodProphecy)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
219
    {
220
        return sprintf(
221
            "  - %s(\n".
222
            "%s\n".
223
            "    )",
224
            $methodProphecy->getMethodName(),
225
            implode(
226
                ",\n",
227
                $this->indentArguments(
228
                    array_map(
229
                        function ($token) {
230
                            return (string) $token;
231
                        },
232
                        $methodProphecy->getArgumentsWildcard()->getTokens()
233
                    ),
234
                    $indentationLength
0 ignored issues
show
Bug introduced by
The variable $indentationLength does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
235
                )
236
            )
237
        );
238
    }
239
240
    private function indentArguments(array $arguments, $indentationLength)
241
    {
242
        return preg_replace_callback(
243
            '/^/m',
244
            function () use ($indentationLength) {
245
                return str_repeat(' ', $indentationLength);
246
            },
247
            $arguments
248
        );
249
    }
250
251
    /**
252
     * @param ObjectProphecy $prophecy
253
     * @param string $methodName
254
     * @param array $arguments
255
     *
256
     * @return array
257
     */
258
    private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments)
259
    {
260
        $matches = array();
261
        foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
262
            if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
263
                $matches[] = array($score, $methodProphecy);
264
            }
265
        }
266
267
        return $matches;
268
    }
269
}
270