Passed
Push — master ( 85a956...018647 )
by Jean
02:31
created

DeferredCallChain::__invoke()   D

Complexity

Conditions 19
Paths 29

Size

Total Lines 68
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 38
c 3
b 0
f 0
dl 0
loc 68
rs 4.5166
cc 19
nc 29
nop 1

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
 * DeferredCallChain
4
 *
5
 * @package php-deferred-callchain
6
 * @author  Jean Claveau
7
 */
8
namespace JClaveau\Async;
9
use       JClaveau\Async\Exceptions\BadTargetClassException;
10
use       JClaveau\Async\Exceptions\BadTargetTypeException;
11
use       JClaveau\Async\Exceptions\UndefinedTargetClassException;
12
use       JClaveau\Async\Exceptions\BadTargetInterfaceException;
13
use       JClaveau\Async\Exceptions\TargetAlreadyDefinedException;
14
use       BadMethodCallException;
15
16
/**
17
 * This class stores an arbitrary stack of calls (methods or array entries access)
18
 * that will be callable on any future variable.
19
 */
20
class DeferredCallChain implements \JsonSerializable, \ArrayAccess
21
{
22
    use \JClaveau\Traits\Fluent\New_;
23
    use FunctionCallTrait;
24
    
25
    /** @var array $stack The stack of deferred calls */
26
    protected $stack = [];
27
28
    /** @var mixed $expectedTarget The stack of deferred calls */
29
    protected $expectedTarget;
30
31
    /**
32
     * Constructor 
33
     * 
34
     * @param string $key The entry to acces
35
     */
36
    public function __construct($class_type_or_instance=null)
37
    {
38
        if ($class_type_or_instance) {
39
            $this->expectedTarget = $class_type_or_instance;
40
        }
41
    }
42
43
    /**
44
     * ArrayAccess interface
45
     *
46
     * @param string $key The entry to acces
47
     */
48
    public function &offsetGet($key)
49
    {
50
        $this->stack[] = [
51
            'entry' => $key,
52
        ];
53
54
        return $this;
55
    }
56
57
    /**
58
     * Stores any call in the the stack.
59
     *
60
     * @param  string $method
61
     * @param  array  $arguments
62
     *
63
     * @return $this
64
     */
65
    public final function __call($method, array $arguments)
66
    {
67
        $this->stack[] = [
68
            'method'    => $method,
69
            'arguments' => $arguments,
70
        ];
71
72
        return $this;
73
    }
74
75
    /**
76
     * For implementing JsonSerializable interface.
77
     *
78
     * @see https://secure.php.net/manual/en/jsonserializable.jsonserialize.php
79
     */
80
    public function jsonSerialize()
81
    {
82
        return $this->stack;
83
    }
84
85
    /**
86
     * Outputs the PHP code producing the current call chain while it's casted
87
     * as a string.
88
     *
89
     * @return string The PHP code corresponding to this call chain
90
     */
91
    public function __toString()
92
    {
93
        $string = '(new ' . get_called_class();
94
        if (is_string($this->expectedTarget)) {
95
            $string .= '(' . var_export($this->expectedTarget, true) . ')';
96
        }
97
        elseif (is_object($this->expectedTarget)) {
98
            $string .= '( ' . get_class($this->expectedTarget) . '#' . spl_object_id($this->expectedTarget) . ' )';
99
        }
100
        $string .= ')';
101
102
        foreach ($this->stack as $i => $call) {
103
            if (isset($call['method'])) {
104
                $string .= '->';
105
                $string .= $call['method'].'(';
106
                $string .= implode(', ', array_map(function($argument) {
107
                    return var_export($argument, true);
108
                }, $call['arguments']));
109
                $string .= ')';
110
            }
111
            else {
112
                $string .= '[' . var_export($call['entry'], true) . ']';
113
            }
114
        }
115
116
        return $string;
117
    }
118
119
    /**
120
     * Checks that the provided target matches the type/class/interface
121
     * given during construction.
122
     * 
123
     * @param  mixed $target
124
     * @return mixed $target Checked
125
     */
126
    protected function checkTarget($target)
127
    {
128
        if (is_object($this->expectedTarget)) {
129
            if ($target) {
130
                throw new TargetAlreadyDefinedException($this, $this->expectedTarget, $target);
131
            }
132
            
133
            $out = $this->expectedTarget;
134
        }
135
        elseif (is_string($this->expectedTarget)) {
136
            if (class_exists($this->expectedTarget)) {
137
                if (! $target instanceof $this->expectedTarget) {
138
                    throw new BadTargetClassException($this, $this->expectedTarget, $target);
139
                }
140
            }
141
            elseif (interface_exists($this->expectedTarget)) {
142
                if (! $target instanceof $this->expectedTarget) {
143
                    throw new BadTargetInterfaceException($this, $this->expectedTarget, $target);
144
                }
145
            }
146
            elseif (type_exists($this->expectedTarget)) {
147
                if (gettype($target) != $this->expectedTarget) {
148
                    throw new BadTargetTypeException($this, $this->expectedTarget, $target);
149
                }
150
            }
151
            else {
152
                throw new UndefinedTargetClassException($this, $this->expectedTarget);
153
            }
154
            
155
            $out = $target;
156
        }
157
        else {
158
            $out = $target;
159
        }
160
        
161
        return $out;
162
    }
163
164
    /**
165
     * Invoking the instance produces the call of the stack
166
     *
167
     * @param  $target The target to apply the callchain on
0 ignored issues
show
Bug introduced by
The type JClaveau\Async\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
168
     * @return The value returned once the call chain is called uppon $target
169
     */
170
    public function __invoke($target=null)
171
    {
172
        $out = $this->checkTarget($target);
173
        
174
        foreach ($this->stack as $i => $call) {
175
            $is_called = false;
176
            try {
177
                if (isset($call['method'])) {
178
                    if (is_callable([$out, $call['method']])) {
179
                        $is_called = true;
180
                        try {
181
                            $out = call_user_func_array([$out, $call['method']], $call['arguments']);
182
                        }
183
                        catch (\BadMethodCallException $e) {
184
                            // Calling a method coded inside a magic __call
185
                            // can produce a BadMethodCallException and thus
186
                            // not be a callable
187
                            if (
188
                                (
189
                                    (  PHP_VERSION_ID < 70000
190
                                        && $e->getTrace()[2]['file'] == __FILE__
191
                                        && $e->getTrace()[2]['function'] == 'call_user_func_array'
192
                                        && $e->getTrace()[1]['function'] == $call['method']
193
                                        && $e->getTrace()[1]['class'] == get_class($out)
194
                                    )
195
                                    ||
196
                                    (   PHP_VERSION_ID >= 70000
197
                                        && $e->getTrace()[1]['file'] == __FILE__
198
                                        && $e->getTrace()[1]['function'] == 'call_user_func_array'
199
                                        // The magic method call doesn't exist in the stack with PHP 7
200
                                    )
201
                                )
202
                                && $e->getTrace()[0]['function'] == '__call'
203
                                && $e->getTrace()[0]['class'] == get_class($out)
204
                            ) {
205
                                $is_called = false;
206
                            }
207
                            else {
208
                                throw $e;
209
                            }
210
                        }
211
                    }
212
                    
213
                    if (! $is_called && is_callable($call['method'])) {
214
                        $arguments = $this->prepareArgs($call['arguments'], $out);
215
                        
216
                        $out = call_user_func_array($call['method'], $arguments);
217
                        $is_called = true;
218
                    }
219
                    
220
                    if (! $is_called) {
221
                        throw new \BadMethodCallException(
222
                            $call['method'] . "() is neither a method of " . get_class($out)
223
                            . " nor a function"
224
                        );
225
                    }
226
                }
227
                else {
228
                    $out = $out[ $call['entry'] ];
229
                }
230
            }
231
            catch (\Exception $e) {
232
                // Throw $e with the good stack (usage exception)
233
                throw $e;
234
            }
235
        }
236
237
        return $out;
238
    }
239
240
    /**
241
     * Unused part of the ArrayAccess interface
242
     *
243
     * @param  $offset
244
     * @param  $value
245
     * @throws \BadMethodCallException
246
     */
247
    public function offsetSet($offset, $value)
248
    {
249
        throw new BadMethodCallException(
250
            "not implemented"
251
        );
252
    }
253
254
    /**
255
     * Unused part of the ArrayAccess interface
256
     *
257
     * @param  $offset
258
     * @throws \BadMethodCallException
259
     */
260
    public function offsetExists($offset)
261
    {
262
        throw new BadMethodCallException(
263
            "not implemented"
264
        );
265
    }
266
267
    /**
268
     * Unused part of the ArrayAccess interface
269
     *
270
     * @param  $offset
271
     * @throws \BadMethodCallException
272
     */
273
    public function offsetUnset($offset)
274
    {
275
        throw new BadMethodCallException(
276
            "not implemented"
277
        );
278
    }
279
280
    /**/
281
}
282