Completed
Push — master ( f66d75...ad4e62 )
by devosc
02:38
created

Resolver::callable()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 8.8571
cc 5
eloc 6
nc 5
nop 1
crap 5
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\Dependency;
17
use Mvc5\Plugin\Gem\Factory;
18
use Mvc5\Plugin\Gem\Filter;
19
use Mvc5\Plugin\Gem\Invokable;
20
use Mvc5\Plugin\Gem\Invoke;
21
use Mvc5\Plugin\Gem\Link;
22
use Mvc5\Plugin\Gem\Param;
23
use Mvc5\Plugin\Gem\Plug;
24
use Mvc5\Plugin\Gem\Plugin;
25
use Mvc5\Resolvable;
26
use Mvc5\Service\Config as Container;
27
use Mvc5\Service\Container as ServiceContainer;
28
use Mvc5\Service\Manager as ServiceManager;
29
use ReflectionClass;
30
use RuntimeException;
31
32
trait Resolver
33
{
34
    /**
35
     *
36
     */
37
    use Container;
38
    use Generator;
39
    use Initializer;
40
41
    /**
42
     * @param $args
43
     * @return array|callable|null|object|string
44
     */
45
    protected function args($args)
46
    {
47
        if (!$args) {
48 12
            return $args;
49
        }
50 12
51 6
        if (!is_array($args)) {
52
            return $this->resolve($args);
53
        }
54 11
55 8
        foreach($args as $index => $value) {
56
            $value instanceof Resolvable && $args[$index] = $this->resolve($value);
57
        }
58 6
59 6
        return $args;
60
    }
61
62 6
    /**
63
     * @param array $config
64
     * @param array $args
65
     * @param callable $callback
66
     * @param bool $plugin
67
     * @return callable|object
68
     */
69
    protected function build(array $config, array $args = [], callable $callback = null, $plugin = false)
70
    {
71 4
        return $this->combine(array_shift($config), $config, $args, $callback, $plugin);
72
    }
73 4
74
    /**
75
     * @param array|callable|object|string $config
76
     * @param array $args
77
     * @param callable $callback
78
     * @return callable|mixed|null|object
79
     * @throws \RuntimeException
80
     */
81
    public function call($config, array $args = [], callable $callback = null)
82
    {
83 11
        if (is_string($config)) {
84
            return $this->transmit(explode(Arg::CALL_SEPARATOR, $config), $args, $callback);
85 11
        }
86 2
87 2
        if ($config instanceof Event) {
88
            return $this->event($config, $args, $callback);
89
        }
90 9
91 9
        return $this->invoke($config, $args, $callback);
92 9
    }
93
94
    /**
95 3
     * @param array|callable|object|string $config
96 1
     * @return callable|null
97 3
     */
98
    protected function callable($config) : callable
99 9
    {
100
        if (is_string($config)) {
101 9
            return function($args = []) use($config) { return $this->call($config, $args); };
102 1
        }
103
104
        if (is_array($config)) {
105 8
            return is_string($config[0]) ? $config : [$this->resolve($config[0]), $config[1]];
106 1
        }
107
108
        return $config instanceof Closure ? $config : $this->listener($this->resolve($config));
0 ignored issues
show
Bug introduced by
It seems like $this->resolve($config) targeting Mvc5\Resolver\Resolver::resolve() can also be of type null or object<Mvc5\Resolvable>; however, Mvc5\Resolver\Generator::listener() 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...
109 8
    }
110
111
    /**
112
     * @param $name
113
     * @param array $args
114
     * @param callable|null $callback
115
     * @return callable|object
116
     */
117 1
    protected function callback($name, array $args = [], callable $callback = null)
118
    {
119 1
        return $callback && !class_exists($name) ? $callback($name) : $this->make($name, $args);
120
    }
121
122
    /**
123
     * @param Child $config
124
     * @param array $args
125
     * @return array|callable|object|string
126
     */
127
    protected function child(Child $config, array $args = [])
128
    {
129 5
        return $this->provide($this->merge(clone $this->parent($config->parent()), $config), $args);
130
    }
131 5
132 3
    /**
133 1
     * @param $name
134 3
     * @param array $config
135
     * @param array $args
136
     * @param callable $callback
137
     * @param bool $plugin
138 5
     * @return callable|object
139
     */
140
    protected function combine($name, array $config, array $args = [], callable $callback = null, $plugin = false)
141
    {
142
        return $this->compose(
143
            $this->create($name, $args, $callback, $plugin || $config), $config, $args, $callback
0 ignored issues
show
Bug Best Practice introduced by
The expression $config 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...
144
        );
145
    }
146
147 6
    /**
148
     * @param $plugin
149 6
     * @param array $config
150 6
     * @param array $args
151
     * @param callable $callback
152
     * @return callable|object
153
     */
154
    protected function compose($plugin, array $config = [], array $args = [], callable $callback = null)
155
    {
156
        foreach($config as $name) {
157
            $plugin = $plugin instanceof ServiceManager ? $plugin->plugin($name, $args, $callback) : (
158
                $plugin instanceof ServiceContainer ? $this->plugin($plugin[$name], $args, $callback) :
159 1
                    $this->resolve($plugin[$name], $args)
160
            );
161 1
        }
162 1
163
        return $plugin;
164
    }
165 1
166
    /**
167
     * @param $name
168
     * @param array $args
169
     * @param callable $callback
170
     * @param bool $plugin
171
     * @return callable|object
172 2
     */
173
    protected function create($name, array $args = [], callable $callback = null, $plugin = true)
174 2
    {
175
        return ($plugin ? $this->unique($name, $this->configured($name), $args, $callback) : null) ??
176
            $this->callback($name, $args, $callback);
177
    }
178
179
    /**
180
     * @param array|callable|null|object|string $arg
181
     * @param array $filters
182 11
     * @return mixed
183
     */
184 11
    protected function filter($arg, array $filters)
185 10
    {
186 3
        foreach($filters as $filter) {
187 1
            $arg = $filter($arg);
188 1
        }
189
190
        return $arg;
191 2
    }
192 1
193 1
    /**
194
     * @param string $name
195
     * @return mixed
196 1
     */
197 1
    public function get($name)
198
    {
199
        return $this->shared($name) ?? $this->plugin($name);
200 7
    }
201 6
202 6
    /**
203
     * @param Plugin $config
204 6
     * @param object $service
205 3
     * @return object
206 3
     */
207
    protected function hydrate(Plugin $config, $service)
208
    {
209 6
        foreach($config->calls() as $method => $args) {
210 6
            if (is_string($method)) {
211 6
                if (Arg::INDEX == $method[0]) {
212
                    $service[substr($method, 1)] = $this->resolve($args);
213
                    continue;
214 6
                }
215
216
                if (Arg::PROPERTY == $method[0]) {
217 1
                    $service->{substr($method, 1)} = $this->resolve($args);
218
                    continue;
219
                }
220 11
221
                $service->$method($this->resolve($args));
222
                continue;
223
            }
224
225
            if (is_array($args)) {
226
                $method = array_shift($args);
227 5
                $param  = $config->param();
228
229 5
                if (is_string($method) && Arg::PROPERTY == $method[0]) {
230
                    $param  = substr($method, 1);
231
                    $method = array_shift($args);
232
                }
233 4
234 2
                $this->invoke(
235
                    is_string($method) ? [$service, $method] : $method,
236
                    ($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...
237 2
                );
238
239
                continue;
240
            }
241
242
            $this->resolve($args);
243
        }
244
245
        return $service;
246 10
    }
247
248 10
    /**
249
     * @param array|callable|object|string $name
250
     * @return callable|null
251
     */
252
    protected function invokable($name)
253
    {
254
        return Arg::CALL === $name[0] ? substr($name, 1) : $this->listener($this->plugin($name, [], function($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...
Bug introduced by
It seems like $this->plugin($name, arr...g::EVENT => $name)); }) targeting Mvc5\Resolver\Resolver::plugin() can also be of type null or object<Mvc5\Resolvable>; however, Mvc5\Resolver\Generator::listener() 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...
255 2
            return $this->create(Arg::EVENT_MODEL, [Arg::EVENT => $name]);
256
        }));
257 2
    }
258
259
    /**
260
     * @param array|callable|object|string $config
261
     * @param array $args
262
     * @param callable $callback
263
     * @return array|callable|object|string
264
     */
265
    protected function invoke($config, array $args = [], callable $callback = null)
266
    {
267 7
        return $this->signal($config, $args, $callback ?? $this);
268
    }
269 7
270
    /**
271 7
     * @param string $name
272 1
     * @param array $args
273
     * @return callable|object
274
     */
275 6
    protected function make($name, array $args = [])
276 1
    {
277
        $class = new ReflectionClass($name);
278
279 5
        if (!$class->hasMethod('__construct')) {
280 5
            return $class->newInstanceWithoutConstructor();
281
        }
282 5
283 4
        if ($args && !is_string(key($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...
284 3
            return $class->newInstanceArgs($this->args($args));
285 3
        }
286
287
        $matched = [];
288 4
        $params  = $class->getConstructor()->getParameters();
289 3
290 3
        foreach($params as $param) {
291 3
            if (isset($args[$param->name])) {
292
                $matched[] = $this->resolve($args[$param->name]);
293
                continue;
294 3
            }
295 1
296 1
            if ($param->isOptional()) {
297
                $param->isDefaultValueAvailable() &&
298
                $matched[] = $param->getDefaultValue();
299 2
                continue;
300 1
            }
301 1
302
            if (null !== ($hint = $param->getClass()) && null !== $match = $this($hint->name)) {
303
                $matched[] = $match;
304 1
                continue;
305
            }
306
307 4
            if (null !== $match = $this($param->name)) {
308
                $matched[] = $match;
309
                continue;
310
            }
311
312
            throw new RuntimeException('Missing required parameter $' . $param->name . ' for ' . $name);
313
        }
314
315 3
        return $class->newInstanceArgs($params ? $matched : $this->args($args));
316
    }
317 3
318
    /**
319 3
     * @param Plugin $parent
320 1
     * @param Plugin $config
321 1
     * @return Plugin
322
     */
323
    protected function merge(Plugin $parent, Plugin $config)
324 3
    {
325 2
        !$parent->name() &&
326 2
            $parent[Arg::NAME] = $this->resolve($config->name());
327
328
        $config->args() &&
329 3
            $parent[Arg::ARGS] = is_string(key($config->args())) ? $config->args() + $parent->args() : $config->args();
330
331 3
        $config->calls() &&
332
            $parent[Arg::CALLS] = $config->merge() ? array_merge($parent->calls(), $config->calls()) : $config->calls();
333
334
        $config->param() &&
335
            $parent[Arg::PARAM] = $config->param();
336
337
        return $parent;
338 1
    }
339
340 1
    /**
341 1
     * @param string $name
342
     * @return mixed
343 1
     */
344 1
    public function param($name)
345
    {
346
        $name  = explode(Arg::CALL_SEPARATOR, $name);
347 1
        $value = $this->config()[array_shift($name)];
348
349
        foreach($name as $n) {
350
            $value = $value[$n];
351
        }
352
353
        return $value;
354 1
    }
355
356 1
    /**
357
     * @param $config
358
     * @return array|callable|Plugin|null|object|string
359
     */
360
    protected function parent($config)
361
    {
362
        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...
363
    }
364
365 12
    /**
366
     * @param string $config
367 12
     * @param array $args
368 5
     * @param callable|null $callback
369
     * @return array|callable|null|object|string
370
     */
371 11
    public function plugin($config, array $args = [], callable $callback = null)
372 8
    {
373 8
        if (!$config) {
374
            return $config;
375
        }
376 3
377 1
        if (is_string($config)) {
378
            return $this->build(explode(Arg::SERVICE_SEPARATOR, $config), $args, $callback, true);
379
        }
380 2
381 1
        if (is_array($config)) {
382
            return $this->plugin(array_shift($config), $args + $config, $callback);
383
        }
384 1
385
        if ($config instanceof Closure) {
386
            return $this->invoke($config, $args, $callback);
387
        }
388
389
        return $this->resolve($config, $args);
390
    }
391
392 3
    /**
393
     * @param Plugin $config
394 3
     * @param array $args
395 3
     * @return callable|null|object
396
     */
397 3
    protected function provide(Plugin $config, array $args = [])
398
    {
399 3
        $name   = $this->solve($config->name());
400
        $parent = $this->configured($name);
401 3
402 2
        $args && is_string(key($args)) && $config->args() && $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...
403
404
        !$args && $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...
405 2
406 1
        if (!$parent) {
407
            return $this->hydrate($config, $this->build(explode(Arg::SERVICE_SEPARATOR, $name), $args));
0 ignored issues
show
Documentation introduced by
$this->build(explode(\Mv...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...
408
        }
409 1
410
        if (!$parent instanceof Plugin) {
411
            return $this->hydrate(
412
                $config, $name === $parent ? $this->make($name, $args) : $this->plugin($this->solve($parent), $args)
0 ignored issues
show
Bug introduced by
It seems like $name === $parent ? $thi...>solve($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...
413
            );
414
        }
415
416
        if ($name == $parent->name()) {
417 24
            return $this->hydrate($config, $this->make($name, $args));
418
        }
419 24
420 12
        return $this->provide($this->merge(clone $parent, $config), $args);
421
    }
422
423 16
    /**
424 1
     * @param $plugin
425
     * @param array $config
426
     * @param array $args
427 15
     * @param callable|null $callback
428 1
     * @return array|callable|object|string
429
     */
430
    protected function relay($plugin, array $config = [], array $args = [], callable $callback = null)
431 14
    {
432 1
        return !$config ? $this->invoke($plugin, $args, $callback) :
433
            $this->relay([$plugin, array_shift($config)], $config, $args, $callback);
434
    }
435 13
436 1
    /**
437
     * @param $config
438
     * @param array $args
439 12
     * @param callable $callback
440 2
     * @return array|callable|Plugin|null|object|Resolvable|string
441
     * @throws RuntimeException
442
     */
443 10
    protected function resolvable($config, array $args = [], callable $callback = null)
444 1
    {
445
        if (!$config instanceof Resolvable) {
446
            return $config;
447 9
        }
448 1
449
        if ($config instanceof Factory) {
450
            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...
451 8
        }
452 1
453
        if ($config instanceof Calls) {
454
            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...
455 7
        }
456 1
457
        if ($config instanceof Child) {
458
            return $this->child($config, $args);
459 6
        }
460 1
461
        if ($config instanceof Plugin) {
462
            return $this->provide($config, $args);
463 5
        }
464 1
465
        if ($config instanceof Dependency) {
466
            return $this->shared($config->name()) ?? $this->initialize($config->name(), $config->config());
467 4
        }
468 1
469
        if ($config instanceof Param) {
470
            return $this->resolve($this->param($config->name()), $args);
471 3
        }
472
473 1
        if ($config instanceof Call) {
474 1
            return $this->call($config->config(), $args + $this->args($config->args()));
475
        }
476
477 2
        if ($config instanceof Args) {
478
            return $this->args($config->config());
479 1
        }
480 1
481
        if ($config instanceof Config) {
482
            return $this->config();
483 1
        }
484
485
        if ($config instanceof Link) {
486
            return $this;
487
        }
488
489
        if ($config instanceof Filter) {
490 2
            return $this->filter($this->resolve($config->config()), $config->filter());
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...
Bug introduced by
It seems like $config->filter() targeting Mvc5\Plugin\Gem\Filter::filter() can also be of type string; however, Mvc5\Resolver\Resolver::filter() does only seem to accept array, 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...
491
        }
492 2
493
        if ($config instanceof Plug) {
494
            return is_string($config->name()) ? $this->configured($config->name()) : $config->name();
495
        }
496
497
        if ($config instanceof Invoke) {
498
            return function(array $args = []) use ($config) {
499
                return $this->call($this->solve($config->config()), $args + $this->args($config->args()));
0 ignored issues
show
Documentation introduced by
$this->solve($config->config()) is of type *, but the function expects a callable.

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...
500
            };
501 1
        }
502
503 1
        if ($config instanceof Invokable) {
504 1
            return function(array $args = []) use ($config) {
505
                return $this->solve($this->resolve($config->config(), $args + $config->args()));
506
            };
507
        }
508
509
        return $callback ? $callback($config) : $this->resolver($config);
510
    }
511
512
    /**
513
     * @param $config
514
     * @param array $args
515
     * @return array|callable|Plugin|null|object|Resolvable|string
516
     * @throws RuntimeException
517
     */
518
    protected function resolve($config, array $args = [])
519
    {
520
        return $this->resolvable($config, $args);
521
    }
522
523
    /**
524
     * @param $config
525
     * @return callable|mixed|null|object
526
     */
527
    protected function resolver($config)
528
    {
529
        return $this->call(Arg::SERVICE_RESOLVER, [Arg::PLUGIN => $config]);
530
    }
531
532
    /**
533
     * @param $config
534
     * @param int $c
535
     * @return mixed
536
     */
537
    protected function solve($config, $c = 0)
538
    {
539
        return !$config instanceof Resolvable ? $config : (
540
            $c < Arg::MAX_RECURSION ?
541
                $this->solve($this->resolve($config), ++$c) : $this->signal(new Exception, [Arg::PLUGIN => $config])
542
        );
543
    }
544
545
    /**
546
     * @param array $config
547
     * @param array $args
548
     * @param callable|null $callback
549
     * @return array|callable|object|string
550
     */
551
    protected function transmit(array $config = [], array $args = [], callable $callback = null)
552
    {
553
        return $this->relay($this->invokable(array_shift($config)), $config, $args, $callback);
554
    }
555
556
    /**
557
     * @param array|object|string|\Traversable $event
558
     * @param array $args
559
     * @param callable $callback
560
     * @return mixed|null
561
     */
562
    public function trigger($event, array $args = [], callable $callback = null)
563
    {
564
        return $this->event($event instanceof Event ? $event : $this($event) ?? $event, $args, $callback);
565
    }
566
567
    /**
568
     * @param $name
569
     * @param $config
570
     * @param array $args
571
     * @param callable $callback
572
     * @return callable|object
573
     */
574
    protected function unique($name, $config, array $args = [], callable $callback = null)
575
    {
576
        return $name === $config ? $this->callback($name, $args, $callback) : $this($config, $args);
577
    }
578
579
    /**
580
     * @param string $name
581
     * @param callable $callback
582
     * @param array $args
583
     * @return array|callable|null|object|string
584
     */
585
    public function __invoke($name, array $args = [], callable $callback = null)
586
    {
587
        return $this->plugin($name, $args, $callback ?? function(){});
588
    }
589
}
590