Completed
Push — master ( a9a146...85a956 )
by Jean
02:11
created

DeferredCallChain::checkTarget()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 36
rs 7.6666
cc 10
nc 10
nop 1

How to fix   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
                                   $e->getTrace()[2]['file'] == __FILE__
189
                                && $e->getTrace()[2]['function'] == 'call_user_func_array'
190
                                && $e->getTrace()[1]['function'] == $call['method']
191
                                && $e->getTrace()[1]['class'] == get_class($out)
192
                                && $e->getTrace()[0]['function'] == '__call'
193
                                && $e->getTrace()[0]['class'] == get_class($out)
194
                            ) {
195
                                $is_called = false;
196
                            }
197
                            else {
198
                                throw $e;
199
                            }
200
                        }
201
                    }
202
                    
203
                    if (! $is_called && is_callable($call['method'])) {
204
                        $arguments = $this->prepareArgs($call['arguments'], $out);
205
                        
206
                        $out = call_user_func_array($call['method'], $arguments);
207
                        $is_called = true;
208
                    }
209
                    
210
                    if (! $is_called) {
211
                        throw new \BadMethodCallException(
212
                            $call['method'] . "() is neither a method of " . get_class($out)
213
                            . " nor a function"
214
                        );
215
                    }
216
                }
217
                else {
218
                    $out = $out[ $call['entry'] ];
219
                }
220
            }
221
            catch (\Exception $e) {
222
                // Throw $e with the good stack (usage exception)
223
                throw $e;
224
            }
225
        }
226
227
        return $out;
228
    }
229
230
    /**
231
     * Unused part of the ArrayAccess interface
232
     *
233
     * @param  $offset
234
     * @param  $value
235
     * @throws \BadMethodCallException
236
     */
237
    public function offsetSet($offset, $value)
238
    {
239
        throw new BadMethodCallException(
240
            "not implemented"
241
        );
242
    }
243
244
    /**
245
     * Unused part of the ArrayAccess interface
246
     *
247
     * @param  $offset
248
     * @throws \BadMethodCallException
249
     */
250
    public function offsetExists($offset)
251
    {
252
        throw new BadMethodCallException(
253
            "not implemented"
254
        );
255
    }
256
257
    /**
258
     * Unused part of the ArrayAccess interface
259
     *
260
     * @param  $offset
261
     * @throws \BadMethodCallException
262
     */
263
    public function offsetUnset($offset)
264
    {
265
        throw new BadMethodCallException(
266
            "not implemented"
267
        );
268
    }
269
270
    /**/
271
}
272