Completed
Push — master ( 7b5e8d...349330 )
by devosc
03:32
created

Resolver::unserialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 *
4
 */
5
6
namespace Mvc5\Resolver;
7
8
use Closure;
9
use Mvc5\Arg;
10
use Mvc5\Event\Event;
11
use Mvc5\Plugin\Gem\Args;
12
use Mvc5\Plugin\Gem\Call;
13
use Mvc5\Plugin\Gem\Calls;
14
use Mvc5\Plugin\Gem\Child;
15
use Mvc5\Plugin\Gem\Config;
16
use Mvc5\Plugin\Gem\Copy;
17
use Mvc5\Plugin\Gem\Factory;
18
use Mvc5\Plugin\Gem\FileInclude;
19
use Mvc5\Plugin\Gem\Filter;
20
use Mvc5\Plugin\Gem\Gem;
21
use Mvc5\Plugin\Gem\Invokable;
22
use Mvc5\Plugin\Gem\Invoke;
23
use Mvc5\Plugin\Gem\Link;
24
use Mvc5\Plugin\Gem\Param;
25
use Mvc5\Plugin\Gem\Plug;
26
use Mvc5\Plugin\Gem\Plugin;
27
use Mvc5\Plugin\Gem\Provide;
28
use Mvc5\Plugin\Gem\Scoped;
29
use Mvc5\Plugin\Gem\Shared;
30
use Mvc5\Plugin\Gem\SignalArgs;
31
use Mvc5\Plugin\Gem\Value;
32
use Mvc5\Resolvable;
33
use Mvc5\Service\Config\Container;
34
35
trait Resolver
36
{
37
    /**
38
     *
39
     */
40
    use Build;
41
    use Container;
42
    use Generator;
43
    use Initializer;
44
45
    /**
46
     * @var callable
47
     */
48
    protected $provider;
49
50
    /**
51
     * @var object
52
     */
53
    protected $scope;
54
55
    /**
56
     * @var bool
57
     */
58
    protected $strict = false;
59
60
    /**
61
     * @param array|\ArrayAccess $config
62
     * @param callable $provider
63
     * @param object $scope
64
     * @param bool $strict
65 283
     */
66
    function __construct($config = null, callable $provider = null, $scope = null, $strict = false)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
67 283
    {
68
        $config && $this->config = $config;
0 ignored issues
show
Documentation Bug introduced by
It seems like $config can also be of type object<ArrayAccess>. However, the property $config is declared as type array|object<Mvc5\Config\Configuration>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
69 283
70 3
        isset($config[Arg::CONTAINER])
71
            && $this->container = $config[Arg::CONTAINER];
72 283
73 32
        isset($config[Arg::EVENTS])
74
            && $this->events = $config[Arg::EVENTS];
75 283
76 74
        isset($config[Arg::SERVICES])
77
            && $this->services = $config[Arg::SERVICES];
78 283
79
        $provider && $this->provider = $this->resolve($provider);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->resolve($provider) can also be of type object<Mvc5\Resolvable>. However, the property $provider is declared as type callable. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
80 283
81
        $scope && $this->scope = $this->resolve($scope);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->resolve($scope) can also be of type callable. However, the property $scope is declared as type object. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
82 283
83 283
        $strict && $this->strict = $strict;
84
    }
85
86
    /**
87
     * @param $args
88
     * @return array|callable|null|object|string
89 60
     */
90
    protected function args($args)
91 60
    {
92 33
        if (!$args) {
93
            return $args;
94
        }
95 41
96 2
        if (!is_array($args)) {
97
            return $this->resolve($args);
98
        }
99 39
100 39
        foreach($args as $index => $value) {
101
            $value instanceof Resolvable && $args[$index] = $this->resolve($value);
102
        }
103 39
104
        return $args;
105
    }
106
107
    /**
108
     * @param array $child
109
     * @param array $parent
110
     * @return array
111 28
     */
112
    protected function arguments(array $child, array $parent)
113 28
    {
114 28
        return !$parent ? $child : (
115
            !$child ? $parent : (is_string(key($child)) ? $child + $parent : array_merge($child, $parent))
116
        );
117
    }
118
119
    /**
120
     * @param Closure $callback
121
     * @param $scope
122
     * @return Closure
123 5
     */
124
    protected function bind(Closure $callback, $scope)
125 5
    {
126
        return Closure::bind($callback, $scope, $scope);
127
    }
128
129
    /**
130
     * @param array|callable|object|string $config
131
     * @param array $args
132
     * @param callable $callback
133
     * @return callable|mixed|null|object
134 64
     */
135
    function call($config, array $args = [], callable $callback = null)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
136 64
    {
137 46
        if (is_string($config)) {
138
            return $this->transmit(explode(Arg::CALL_SEPARATOR, $config), $args, $callback);
139
        }
140 21
141 1
        if ($config instanceof Event) {
142
            return $this->event($config, $args, $callback);
143
        }
144 20
145
        return $this->invoke($config instanceof Resolvable ? $this->resolve($config) : $config, $args, $callback);
0 ignored issues
show
Bug introduced by
It seems like $config instanceof \Mvc5...olve($config) : $config can also be of type object<Mvc5\Resolvable>; however, Mvc5\Resolver\Resolver::invoke() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
146
    }
147
148
    /**
149
     * @param array|callable|object|string $config
150
     * @return callable|null
151
     */
152
    protected function callable($config) : callable
153 53
    {
154
        if (is_string($config)) {
155 21
            return function(...$args) use($config) {
156 22
                return $this->call($config, $this->variadic($args));
157
            };
158
        }
159 35
160 3
        if (is_array($config)) {
161
            return is_string($config[0]) ? $config : [$this->resolve($config[0]), $config[1]];
162
        }
163 32
164
        return $config instanceof Closure ? $config : $this->listener($this->resolve($config));
165
    }
166
167
    /**
168
     * @param Child $config
169
     * @param array $args
170
     * @return array|callable|object|string
171 3
     */
172
    protected function child(Child $config, array $args = [])
173 3
    {
174
        return $this->provide($this->merge(clone $this->parent($config->parent()), $config), $args);
175
    }
176
177
    /**
178
     * @param $name
179
     * @return callable|mixed|object
180 7
     */
181
    protected function fallback($name)
182 7
    {
183
        return $this(Arg::EVENT_MODEL, [Arg::EVENT => $name]) ?: Unresolvable::plugin($name);
184
    }
185
186
    /**
187
     * @param array|callable|null|object|string $value
188
     * @param array|\Traversable $filters
189
     * @param array $args
190
     * @param $param
191
     * @return mixed
192 10
     */
193
    protected function filter($value, $filters = [], array $args = [], $param = null)
194 10
    {
195
        $result = $value;
196 10
197 9
        foreach($filters as $filter) {
198 9
            $value = $this->invoke(
199
                $this->callable($filter), $param ? [$param => $result] + $args : array_merge([$result], $args)
0 ignored issues
show
Bug introduced by
It seems like $this->callable($filter) targeting Mvc5\Resolver\Resolver::callable() can also be of type null; however, Mvc5\Resolver\Resolver::invoke() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
200
            );
201 9
202 1
            if (false === $value) {
203
                return $result;
204
            }
205 9
206 1
            if (null === $value) {
207
                return null;
208
            }
209 9
210
            $result = $value;
211
        }
212 8
213
        return $result;
214
    }
215
216
    /**
217
     * @param Filter $config
218
     * @param array $args
219
     * @return mixed
220 9
     */
221
    protected function filterable(Filter $config, array $args = [])
222 9
    {
223 9
        return $this->filter(
224
            $this->resolve($config->config()), $this->resolve($config->filter()), $args, $config->param()
0 ignored issues
show
Bug introduced by
It seems like $this->resolve($config->config()) targeting Mvc5\Resolver\Resolver::resolve() can also be of type object<Mvc5\Resolvable>; however, Mvc5\Resolver\Resolver::filter() does only seem to accept callable|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Documentation introduced by
$this->resolve($config->filter()) is of type callable|null|object<Mvc5\Resolvable>, but the function expects a array|object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
225
        );
226
    }
227
228
    /**
229
     * @param $config
230
     * @param array $args
231
     * @return mixed|callable
232 64
     */
233
    protected function gem($config, array $args = [])
234 64
    {
235 1
        if ($config instanceof Factory) {
236
            return $this->invoke($this->child($config, $args));
0 ignored issues
show
Bug introduced by
It seems like $this->child($config, $args) targeting Mvc5\Resolver\Resolver::child() can also be of type null; however, Mvc5\Resolver\Resolver::invoke() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
237
        }
238 63
239 1
        if ($config instanceof Calls) {
240
            return $this->hydrate($config, $this->resolve($config->name(), $args));
0 ignored issues
show
Bug introduced by
It seems like $this->resolve($config->name(), $args) targeting Mvc5\Resolver\Resolver::resolve() can also be of type callable or null; however, Mvc5\Resolver\Resolver::hydrate() does only seem to accept object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
241
        }
242 63
243 1
        if ($config instanceof Child) {
244
            return $this->child($config, $args);
245
        }
246 62
247 25
        if ($config instanceof Plugin) {
248
            return $this->provide($config, $args);
249
        }
250 53
251 5
        if ($config instanceof Shared) {
252
            return $this->shared($config->name(), $config->config());
253
        }
254 51
255 3
        if ($config instanceof Param) {
256
            return $this->resolve($this->param($config->name()), $args);
257
        }
258 50
259 9
        if ($config instanceof Call) {
260
            return $this->call($this->resolve($config->config()), $this->vars($args, $config->args()));
0 ignored issues
show
Bug introduced by
It seems like $this->resolve($config->config()) targeting Mvc5\Resolver\Resolver::resolve() can also be of type null or object<Mvc5\Resolvable>; however, Mvc5\Resolver\Resolver::call() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
261
        }
262 47
263 12
        if ($config instanceof Args) {
264
            return $this->args($config->config());
265
        }
266 41
267 2
        if ($config instanceof Config) {
268
            return $this->config();
269
        }
270 39
271 6
        if ($config instanceof Link) {
272
            return $this;
273
        }
274 34
275 8
        if ($config instanceof Filter) {
276
            return $this->filterable($config, $this->vars($args, $config->args()));
277
        }
278 26
279 3
        if ($config instanceof Plug) {
280
            return $this->configured($config->name());
281
        }
282 24
283
        if ($config instanceof Invoke) {
284 8
            return function(...$args) use ($config) {
285 8
                return $this->call(
286
                    $this->resolve($config->config()), $this->vars($this->variadic($args), $config->args())
0 ignored issues
show
Bug introduced by
It seems like $this->resolve($config->config()) targeting Mvc5\Resolver\Resolver::resolve() can also be of type null or object<Mvc5\Resolvable>; however, Mvc5\Resolver\Resolver::call() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
287 8
                );
288
            };
289
        }
290 19
291
        if ($config instanceof Invokable) {
292 3
            return function(...$args) use ($config) {
293 3
                return $this->resolve($config->config(), $this->vars($this->variadic($args), $config->args()));
294
            };
295
        }
296 16
297
        if ($config instanceof FileInclude) {
298 1
            $include = new class() {
299 1
                function __invoke($file) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
300
                    return include $file;
301
                }
302
            };
303 1
304
            return $include($this->resolve($config->config()));
305
        }
306 15
307 1
        if ($config instanceof Copy) {
308
            return clone $this->resolve($config->config(), $args);
309
        }
310 14
311 10
        if ($config instanceof Value) {
312
            return $config->config();
313
        }
314 4
315 3
        if ($config instanceof Scoped) {
316
            return $this->scoped($config->closure());
317
        }
318 1
319
        if ($config instanceof Provide) {
320
            return ($this->provider() ?: new Unresolvable)($config->config(), $this->vars($args, $config->args()));
321
        }
322
323
        return Unresolvable::plugin($config);
324
    }
325 20
326
    /**
327 20
     * @param string $name
328
     * @return mixed
329
     */
330
    function get($name)
331
    {
332
        return $this->stored($name) ?? $this($name);
333
    }
334
335 37
    /**
336
     * @param Plugin $config
337 37
     * @param object $service
338 11
     * @return object
339 6
     */
340 2
    protected function hydrate(Plugin $config, $service)
341 2
    {
342
        foreach($config->calls() as $method => $args) {
343
            if (is_string($method)) {
344 4
                if (Arg::INDEX == $method[0]) {
345 2
                    $service[substr($method, 1)] = $this->resolve($args);
346 2
                    continue;
347
                }
348
349 2
                if (Arg::PROPERTY == $method[0]) {
350 2
                    $service->{substr($method, 1)} = $this->resolve($args);
351
                    continue;
352
                }
353 5
354 4
                $service->$method($this->resolve($args));
355 4
                continue;
356
            }
357 4
358 2
            if (is_array($args)) {
359 2
                $method = array_shift($args);
360
                $param  = $config->param();
361
362 4
                if (is_string($method) && Arg::PROPERTY == $method[0]) {
363 4
                    $param  = substr($method, 1);
364 4
                    $method = array_shift($args);
365
                }
366
367 4
                $this->invoke(
368
                    is_string($method) ? [$service, $method] : $this->callable($method),
0 ignored issues
show
Bug introduced by
It seems like is_string($method) ? arr...this->callable($method) can also be of type null; however, Mvc5\Resolver\Resolver::invoke() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
369
                    ($param && (!$args || is_string(key($args))) ? [$param => $service] : []) + $this->args($args)
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
370 1
                );
371
372
                continue;
373 37
            }
374
375
            $this->resolve($args);
376
        }
377
378
        return $service;
379
    }
380 53
381
    /**
382 53
     * @param array|callable|object|string $name
383 53
     * @return callable|null
384
     */
385
    protected function invokable($name)
386
    {
387
        return Arg::CALL === $name[0] ? substr($name, 1) :
388
            $this->listener($this->plugin($name, [], $this) ?: $this->fallback($name));
0 ignored issues
show
Documentation introduced by
$name is of type callable, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
389
    }
390
391
    /**
392 85
     * @param array|callable|object|string $config
393
     * @param array $args
394 85
     * @param callable $callback
395
     * @return array|callable|object|string
396
     */
397
    protected function invoke($config, array $args = [], callable $callback = null)
398
    {
399
        return $this->signal($config, $args, $callback ?? $this);
400
    }
401 52
402
    /**
403
     * @param $plugin
404 26
     * @return callable|null
405 52
     */
406
    protected function listener($plugin)
407
    {
408
        return !$plugin instanceof Event ? $plugin : function(...$args) use ($plugin) {
409
            return $this->event($plugin, $this->variadic($args));
410
        };
411
    }
412
413
    /**
414 11
     * @param Plugin $parent
415
     * @param Plugin $config
416 11
     * @param null|string $name
417 3
     * @return Plugin
418
     */
419 11
    protected function merge(Plugin $parent, Plugin $config, $name = null)
420 5
    {
421
        !$parent->name() &&
422 11
            $parent[Arg::NAME] = $name ?? $this->resolve($config->name());
423 2
424
        $config->args() &&
425 11
            $parent[Arg::ARGS] = is_string(key($config->args())) ? $config->args() + $parent->args() : $config->args();
426 8
427
        $config->calls() &&
428 11
            $parent[Arg::CALLS] = $config->merge() ? array_merge($parent->calls(), $config->calls()) : $config->calls();
429
430
        $config->param() &&
431
            $parent[Arg::PARAM] = $config->param();
432
433
        return $parent;
434
    }
435 5
436
    /**
437 5
     * @param string $name
438 5
     * @return mixed
439
     */
440 5
    function param($name)
441 1
    {
442
        $name  = explode(Arg::CALL_SEPARATOR, $name);
443
        $value = $this->config()[array_shift($name)];
444 5
445
        foreach($name as $n) {
446
            $value = $value[$n];
447
        }
448
449
        return $value;
450
    }
451 4
452
    /**
453 4
     * @param $config
454
     * @return array|callable|Plugin|null|object|string
455
     */
456
    protected function parent($config)
457
    {
458
        return $this->configured($this->resolve($config));
0 ignored issues
show
Documentation introduced by
$this->resolve($config) is of type callable|null|object<Mvc5\Resolvable>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
459
    }
460
461
    /**
462
     * @param string $config
463 128
     * @param array $args
464
     * @param callable|null $callback
465 128
     * @param null|string $previous
466 1
     * @return array|callable|null|object|string
467
     */
468
    function plugin($config, array $args = [], callable $callback = null, $previous = null)
469 127
    {
470 115
        if (!$config) {
471
            return $config;
472
        }
473 50
474 6
        if (is_string($config)) {
475
            return $this->build(explode(Arg::SERVICE_SEPARATOR, $config), $args, $callback);
476
        }
477 46
478 18
        if (is_array($config)) {
479
            return $this->pluginArray(array_shift($config), $args + $this->args($config), $callback, $previous);
0 ignored issues
show
Bug introduced by
It seems like $previous defined by parameter $previous on line 468 can also be of type string; however, Mvc5\Resolver\Resolver::pluginArray() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
480
        }
481 32
482
        if ($config instanceof Closure) {
483
            return $this->invoke($this->scoped($config), $args);
484
        }
485
486
        return $this->resolve($config, $args);
487
    }
488
489
    /**
490
     * @param $config
491 7
     * @param array $args
492
     * @param callable|null $callback
493 7
     * @param null $previous
494 7
     * @return array|callable|null|object|string
495
     */
496
    protected function pluginArray($config, array $args = [], callable $callback = null, $previous = null)
497
    {
498
        return $previous && $previous === $config ?
499
            $this->callback($config, true, $args, $callback) : $this->plugin($config, $args, $callback);
500
    }
501
502 28
    /**
503
     * @param Plugin $config
504 28
     * @param array $args
505 28
     * @return callable|null|object
506
     */
507 28
    protected function provide(Plugin $config, array $args = [])
508
    {
509 28
        $name   = $this->resolve($config->name());
510
        $parent = $this->configured($name);
0 ignored issues
show
Documentation introduced by
$name is of type callable|null|object<Mvc5\Resolvable>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
511 28
512 25
        $args && is_string(key($args)) && $config->args() && $args += $this->args($config->args());
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
513
514
        !$args && $args = $this->args($config->args());
515 5
516 3
        if (!$parent) {
517 3
            return $this->hydrate($config, $this->combine(explode(Arg::SERVICE_SEPARATOR, $name), $args));
0 ignored issues
show
Documentation introduced by
$this->combine(explode(\...PARATOR, $name), $args) is of type callable, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
518
        }
519
520
        if (!$parent instanceof Plugin) {
521 2
            return $this->hydrate(
522 1
                $config, $name === $parent ? $this->make($name, $args) : $this->plugin($this->resolve($parent), $args)
0 ignored issues
show
Documentation introduced by
$this->resolve($parent) is of type callable|null|object<Mvc5\Resolvable>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $name === $parent ? $thi...esolve($parent), $args) can also be of type callable or null; however, Mvc5\Resolver\Resolver::hydrate() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
523
            );
524
        }
525 1
526
        if ($name == $parent->name()) {
527
            return $this->hydrate($config, $this->make($name, $args));
0 ignored issues
show
Documentation introduced by
$name is of type callable|null|object<Mvc5\Resolvable>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
528
        }
529
530
        return $this->provide($this->merge(clone $parent, $config, $name), $args);
0 ignored issues
show
Bug introduced by
It seems like $name defined by $this->resolve($config->name()) on line 509 can also be of type callable or object<Mvc5\Resolvable>; however, Mvc5\Resolver\Resolver::merge() does only seem to accept null|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
531 46
    }
532
533 46
    /**
534
     * @return callable
535
     */
536
    protected function provider()
537
    {
538
        return $this->provider;
539
    }
540
541
    /**
542
     * @param $plugin
543 48
     * @param array $config
544
     * @param array $args
545 48
     * @param callable|null $callback
546 47
     * @return array|callable|object|string
547
     */
548
    protected function relay($plugin, array $config = [], array $args = [], callable $callback = null)
549
    {
550
        return !$config ? $this->invoke($plugin, $args, $callback) :
551
            $this->repeat($plugin, array_shift($config), $config, $args, $callback);
552
    }
553
554
    /**
555
     * @param $plugin
556
     * @param $name
557 3
     * @param array $config
558
     * @param array $args
559 3
     * @param callable|null $callback
560 3
     * @return array|callable|object|string
561
     */
562
    protected function repeat($plugin, $name, array $config = [], array $args = [], callable $callback = null)
563
    {
564
        return !$config ? $this->invoke([$plugin, $name], $args, $callback) : $this->repeat(
565
            $this->invoke([$plugin, $name], $args, $callback), array_shift($config), $config, $args, $callback
566
        );
567
    }
568
569
    /**
570
     * @param $config
571 123
     * @param array $args
572
     * @param callable $callback
573 123
     * @param int $c
574 42
     * @return array|callable|Plugin|null|object|Resolvable|string
575 123
     */
576
    protected function resolvable($config, array $args = [], callable $callback = null, $c = 0)
577
    {
578
        return !$config instanceof Resolvable ? $config : (
579
            $c > Arg::MAX_RECURSION ? Unresolvable::plugin($config) :
580
                $this->resolvable($this->solve($config, $args, $callback), $args, $callback, ++$c)
581
        );
582
    }
583
584 122
    /**
585
     * @param $config
586 122
     * @param array $args
587
     * @return array|callable|Plugin|null|object|Resolvable|string
588
     */
589
    protected function resolve($config, array $args = [])
590
    {
591
        return $this->resolvable($config, $args);
592
    }
593
594 4
    /**
595
     * @param $config
596 4
     * @param array $args
597
     * @return callable|mixed|null|object
598
     */
599
    protected function resolver($config, array $args = [])
600
    {
601
        return $this->call($this->provider() ?: Arg::SERVICE_RESOLVER, [$config, $args]);
602
    }
603
604
    /**
605 45
     * @param $config
606
     * @param array $args
607 45
     * @param callable $callback
608 45
     * @return mixed|callable
609
     */
610
    protected function solve($config, array $args = [], callable $callback = null)
611
    {
612
        return $config instanceof Gem ? $this->gem($config, $args) : (
613
            $callback ? $callback($config, $args) : $this->resolver($config, $args)
614
        );
615
    }
616 13
617
    /**
618 13
     * @param object $scope
619
     * @return object
620
     */
621
    function scope($scope = null)
622
    {
623
        return null !== $scope ? $this->scope = $scope : $this->scope;
624
    }
625 21
626
    /**
627 21
     * @param Closure $callback
628
     * @return Closure
629
     */
630
    protected function scoped(Closure $callback)
631
    {
632
        return $this->scope ? $this->bind($callback, $this->scope === true ? $this : $this->scope) : $callback;
633
    }
634
635 8
    /**
636
     * @return string
637 8
     */
638
    function serialize()
639
    {
640
        return serialize([$this->config, $this->events, $this->provider, $this->scope, $this->services, $this->strict]);
641
    }
642
643 100
    /**
644
     * @param $name
645 100
     * @param null $config
646
     * @return callable|mixed|null|object
647
     */
648
    function shared($name, $config = null)
649
    {
650
        return $this->stored($name) ?? $this->initialize($name, $config);
651
    }
652
653
    /**
654 47
     * @return bool
655
     */
656 47
    protected function strict()
657
    {
658
        return $this->strict;
659
    }
660
661
    /**
662
     * @param array $config
663
     * @param array $args
664
     * @param callable|null $callback
665 8
     * @return array|callable|object|string
666
     */
667 8
    protected function transmit(array $config = [], array $args = [], callable $callback = null)
668
    {
669
        return $this->relay($this->invokable(array_shift($config)), $config, $args, $callback);
670
    }
671
672
    /**
673
     * @param array|object|string|\Traversable $event
674 40
     * @param array $args
675
     * @param callable $callback
676 40
     * @return mixed|null
677
     */
678
    function trigger($event, array $args = [], callable $callback = null)
679
    {
680
        return $this->event($event instanceof Event ? $event : $this($event) ?? $event, $args, $callback);
681
    }
682
683
    /**
684 27
     * @param string $serialized
685
     */
686 27
    function unserialize($serialized)
687
    {
688
        list(
689
            $this->config, $this->events, $this->provider, $this->scope, $this->services, $this->strict
690
            ) = unserialize($serialized);
691
    }
692 12
693
    /**
694 12
     * @param array $args
695 2
     * @return array
696
     */
697 12
    protected function variadic(array $args)
698 2
    {
699
        return $args && $args[0] instanceof SignalArgs ? $args[0]->args() : $args;
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
700 2
    }
701 1
702
    /**
703
     * @param array $child
704
     * @param array $parent
705 12
     * @return array
706 2
     */
707
    protected function vars(array $child = [], array $parent = [])
708 2
    {
709 1
        return $this->arguments($child, $this->args($parent));
0 ignored issues
show
Documentation introduced by
$this->args($parent) is of type callable|null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
710
    }
711
712
    /**
713 12
     *
714 2
     */
715
    function __clone()
716 2
    {
717 1
        is_object($this->config) &&
718
            $this->config = clone $this->config;
719
720
        if (is_object($this->container)) {
721 12
            $this->container = clone $this->container;
722 2
723 12
            if (isset($this->config[Arg::CONTAINER])) {
724
                $this->config[Arg::CONTAINER] = $this->container;
725
            }
726
        }
727
728
        if (is_object($this->events)) {
729
            $this->events = clone $this->events;
730
731
            if (isset($this->config[Arg::EVENTS])) {
732
                $this->config[Arg::EVENTS] = $this->events;
733
            }
734
        }
735
736
        if (is_object($this->services)) {
737
            $this->services = clone $this->services;
738
739
            if (isset($this->config[Arg::SERVICES])) {
740
                $this->config[Arg::SERVICES] = $this->services;
741
            }
742
        }
743
744
        is_object($this->scope) &&
745
            $this->scope = clone $this->scope;
746
    }
747
748
    /**
749
     * @param string $name
750
     * @param array $args
751
     * @return array|callable|null|object|string
752
     */
753
    function __invoke($name, array $args = [])
754
    {
755
        return $this->plugin($name, $args, $this->provider() ?? function(){});
756
    }
757
}
758