Passed
Branch master (bb3086)
by Arul
29:39 queued 14:41
created

Async::_setEventLoop()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 4
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace LogicalSteps\Async;
4
5
6
use Closure;
7
use Generator;
8
use Psr\Log\LoggerInterface;
9
use React\EventLoop\LoopInterface;
10
use Amp\Loop\Driver;
11
use React\Promise\Promise;
12
use React\Promise\PromiseInterface;
13
use ReflectionException;
14
use ReflectionFunction;
15
use ReflectionFunctionAbstract;
16
use ReflectionGenerator;
17
use ReflectionMethod;
18
use ReflectionObject;
19
use Throwable;
20
use TypeError;
21
use function GuzzleHttp\Promise\all as guzzleAll;
22
23
/**
24
 * @method static mixed wait($process) synchronously wait for the completion of an asynchronous process
25
 * @method mixed wait($process) synchronously wait for the completion of an asynchronous process
26
 *
27
 * @method static PromiseInterface await($process) await for the completion of an asynchronous process
28
 * @method PromiseInterface await($process) await for the completion of an asynchronous process
29
 *
30
 * @method static void await($process, callable $callback) await for the completion of an asynchronous process
31
 * @method void await($process, callable $callback) await for the completion of an asynchronous process
32
 *
33
 * @method static PromiseInterface awaitAll(array $processes) concurrently await for multiple processes
34
 * @method PromiseInterface awaitAll(array $processes) concurrently await for multiple processes
35
 *
36
 * @method static void awaitAll(array $processes, callable $callback) concurrently await for multiple processes
37
 * @method void awaitAll(array $processes, callable $callback) concurrently await for multiple processes
38
 *
39
 * @method static setLogger(LoggerInterface|null $logger)
40
 * @method setLogger(LoggerInterface|null $logger)
41
 *
42
 * @method static setEventLoop(LoopInterface|Driver|null $loop)
43
 * @method setEventLoop(LoopInterface|Driver|null $loop)
44
 */
45
class Async
46
{
47
    const PROMISE_REACT = 'React\Promise\PromiseInterface';
48
    const PROMISE_AMP = 'Amp\Promise';
49
    const PROMISE_GUZZLE = 'GuzzleHttp\Promise\PromiseInterface';
50
    const PROMISE_HTTP = 'Http\Promise\Promise';
51
52
    /** @var string action to return a promise instead of awaiting the response of the process. */
53
    const promise = 'promise';
54
    /** @var string action to run current process side by side with the remainder of the process. */
55
    const parallel = 'parallel';
56
    /** @var string action to await for all parallel processes previously to finish. */
57
    const all = 'all';
58
    /** @var string action to await for current  processes to finish. this is the default action. */
59
    const await = 'await';
60
    /** @var string action to run current process after finished executing the function. */
61
    const later = 'later';
62
63
    const ACTIONS = [self::await, self::parallel, self::all, self::promise, self::later];
64
65
    public static $knownPromises = [
66
        self::PROMISE_REACT,
67
        self::PROMISE_AMP,
68
        self::PROMISE_GUZZLE,
69
        self::PROMISE_HTTP,
70
    ];
71
72
    /**
73
     * @var LoggerInterface|null
74
     */
75
    protected $logger;
76
77
    /**
78
     * @var LoopInterface|Driver|null;
79
     */
80
    protected $loop;
81
82
83
    /**
84
     * @var bool
85
     */
86
    public $waitForGuzzleAndHttplug = true;
87
88
    /**
89
     * @var bool
90
     */
91
    protected $parallelGuzzleLoading = false;
92
93
    protected $guzzlePromises = [];
94
95
    /**
96
     * Async constructor.
97
     * @param LoggerInterface|null $logger
98
     * @param LoopInterface|Driver|null $eventLoop optional. required when using reactphp or amphp
99
     */
100
    public function __construct(?LoggerInterface $logger = null, $eventLoop = null)
101
    {
102
        $this->logger = $logger;
103
        $this->_setEventLoop($eventLoop);
104
    }
105
106
    public function __call($name, $arguments)
107
    {
108
        $value = null;
109
        $method = "_$name";
110
        switch ($name) {
111
            case 'await':
112
            case 'awaitAll':
113
                if ($this->logger) {
114
                    $this->logger->info('start');
115
                }
116
                if (1 == count($arguments)) {
117
                    //return promise
118
                    list($value, $resolver, $rejector) = $this->makePromise();
119
                    $callback = function ($error = null, $result = null) use ($resolver, $rejector) {
120
                        if ($this->logger) {
121
                            $this->logger->info('end');
122
                        }
123
                        if ($error) {
124
                            return $rejector($error);
125
                        }
126
                        return $resolver($result);
127
                    };
128
                    $arguments[1] = $callback;
129
                } elseif ($this->logger) {
130
                    $c = $arguments[1];
131
                    $callback = function ($error, $result = null) use ($c) {
132
                        $this->logger->info('end');
0 ignored issues
show
Bug introduced by
The method info() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
                        $this->logger->/** @scrutinizer ignore-call */ 
133
                                       info('end');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
133
                        $c($error, $result);
134
                    };
135
                    $arguments[1] = $callback;
136
                }
137
                if ('await' == $name) {
138
                    $arguments[2] = -1;//depth
139
                    $method = '_handle';
140
                }
141
                break;
142
            case 'setLogger':
143
            case 'setEventLoop':
144
                break;
145
            case 'wait':
146
                return call_user_func_array([$this, $method], $arguments);
147
            default:
148
                return null;
149
        }
150
        call_user_func_array([$this, $method], $arguments);
151
        return $value;
152
    }
153
154
    public static function __callStatic($name, $arguments)
155
    {
156
        if (empty($arguments) && in_array($name, self::ACTIONS)) {
157
            return $name;
158
        }
159
        static $instance;
160
        if (!$instance) {
161
            $instance = new static();
162
        }
163
        return $instance->__call($name, $arguments);
164
    }
165
166
    /**
167
     * Throws specified or subclasses of specified exception inside the generator class so that it can be handled.
168
     *
169
     * @param string $throwable
170
     * @return string command
171
     *
172
     * @throws TypeError when given value is not a valid exception
173
     */
174
    public static function throw(string $throwable): string
175
    {
176
        if (is_a($throwable, Throwable::class, true)) {
177
            return __FUNCTION__ . ':' . $throwable;
178
        }
179
        throw new TypeError('Invalid value for throwable, it must extend Throwable class');
180
    }
181
182
    protected function _awaitAll(array $processes, callable $callback): void
183
    {
184
        $this->parallelGuzzleLoading = true;
185
        $results = [];
186
        $failed = false;
187
        foreach ($processes as $key => $process) {
188
            if ($failed)
189
                break;
190
            $c = function ($error = null, $result = null) use ($key, &$results, $processes, $callback, &$failed) {
191
                if ($failed)
192
                    return;
193
                if ($error) {
194
                    $failed = true;
195
                    $callback($error);
196
                    return;
197
                }
198
                $results[$key] = $result;
199
                if (count($results) == count($processes)) {
200
                    $callback(null, $results);
201
                }
202
            };
203
            $this->_handle($process, $c, -1);
204
        }
205
        if (!empty($this->guzzlePromises)) {
206
            guzzleAll($this->guzzlePromises)->wait(false);
207
            $this->guzzlePromises = [];
208
        }
209
        $this->parallelGuzzleLoading = false;
210
    }
211
212
    /**
213
     * Sets a logger instance on the object.
214
     *
215
     * @param LoggerInterface|null $logger
216
     *
217
     * @return void
218
     */
219
    protected function _setLogger(?LoggerInterface $logger)
220
    {
221
        $this->logger = $logger;
222
    }
223
224
    /**
225
     * Sets a logger instance on the object.
226
     *
227
     * @param LoopInterface|Driver|null $loop
228
     *
229
     * @return void
230
     */
231
    protected function _setEventLoop($loop)
232
    {
233
        if ($loop && !($loop instanceof LoopInterface || $loop instanceof Driver)) {
0 ignored issues
show
introduced by
$loop is always a sub-type of Amp\Loop\Driver.
Loading history...
234
            throw new TypeError('Argument 1 passed to LogicalSteps/Async/Async::_setEventLoop() must be ' .
235
                'an instance of React\EventLoop\LoopInterface or use Amp\Loop\Driver or null.');
236
        }
237
        $this->loop = $loop;
238
    }
239
240
    private function makePromise()
241
    {
242
        $resolver = $rejector = null;
243
        $promise = new Promise(function ($resolve, $reject, $notify) use (&$resolver, &$rejector) {
244
            $resolver = $resolve;
245
            $rejector = $reject;
246
        });
247
        return [$promise, $resolver, $rejector];
248
    }
249
250
    protected function _wait($process)
251
    {
252
        if ($this->logger) {
253
            $this->logger->info('start');
254
        }
255
        $waiting = true;
256
        $result = null;
257
        $exception = null;
258
        $isRejected = false;
259
260
        $callback = function ($error = null, $r = null) use (&$result, &$waiting, &$isRejected, &$exception) {
261
            $waiting = false;
262
            if ($this->loop) {
263
                $this->loop->stop();
264
            }
265
            if ($error) {
266
                $isRejected = true;
267
                $exception = $error;
268
                return;
269
            }
270
            $result = $r;
271
        };
272
        $this->_handle($process, $callback, -1);
273
        while ($waiting) {
274
            if ($this->loop) {
275
                $this->loop->run();
276
            }
277
        }
278
        if ($this->logger) {
279
            $this->logger->info('end');
280
        }
281
        if ($isRejected) {
282
            if (!$exception instanceof \Exception) {
283
                $exception = new \UnexpectedValueException(
284
                    'process failed with ' . (is_object($exception) ? get_class($exception) : gettype($exception))
285
                );
286
            }
287
            throw $exception;
288
        }
289
290
        return $result;
291
    }
292
293
    protected function _handle($process, callable $callback, int $depth = 0): void
294
    {
295
        $arguments = [];
296
        $func = [];
297
        if (is_array($process) && count($process) > 1) {
298
            $copy = $process;
299
            $func[] = array_shift($copy);
300
            if (is_callable($func[0])) {
301
                $func = $func[0];
302
            } else {
303
                $func[] = array_shift($copy);
304
            }
305
            $arguments = $copy;
306
        } else {
307
            $func = $process;
308
        }
309
        if (is_callable($func)) {
310
            $this->_handleCallback($func, $arguments, $callback, $depth);
311
        } elseif ($process instanceof Generator) {
312
            $this->_handleGenerator($process, $callback, 1 + $depth);
313
        } elseif (is_object($process) && $implements = array_intersect(class_implements($process),
314
                Async::$knownPromises)) {
315
            $this->_handlePromise($process, array_shift($implements), $callback, $depth);
316
        } else {
317
            $callback(null, $process);
318
        }
319
    }
320
321
322
    protected function _handleCallback(callable $callable, array $parameters, callable $callback, int $depth = 0)
323
    {
324
        $this->logCallback($callable, $parameters, $depth);
325
        try {
326
            if (is_array($callable)) {
327
                $rf = new ReflectionMethod($callable[0], $callable[1]);
328
            } elseif (is_string($callable)) {
329
                $rf = new ReflectionFunction($callable);
330
            } elseif (is_a($callable, 'Closure') || is_callable($callable, '__invoke')) {
0 ignored issues
show
Bug introduced by
$callable of type callable is incompatible with the type object|string expected by parameter $object of is_a(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

330
            } elseif (is_a(/** @scrutinizer ignore-type */ $callable, 'Closure') || is_callable($callable, '__invoke')) {
Loading history...
Bug introduced by
'__invoke' of type string is incompatible with the type boolean expected by parameter $syntax_only of is_callable(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

330
            } elseif (is_a($callable, 'Closure') || is_callable($callable, /** @scrutinizer ignore-type */ '__invoke')) {
Loading history...
331
                $ro = new ReflectionObject($callable);
0 ignored issues
show
Bug introduced by
$callable of type callable is incompatible with the type object expected by parameter $argument of ReflectionObject::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
                $ro = new ReflectionObject(/** @scrutinizer ignore-type */ $callable);
Loading history...
332
                $rf = $ro->getMethod('__invoke');
333
            }
334
            $current = count($parameters);
335
            $total = $rf->getNumberOfParameters();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rf does not seem to be defined for all execution paths leading up to this point.
Loading history...
336
            $ps = $rf->getParameters();
337
            if ($current + 1 < $total) {
338
                for ($i = $current; $i < $total - 1; $i++) {
339
                    $parameters[$i] = $ps[$i]->isDefaultValueAvailable() ? $ps[$i]->getDefaultValue() : null;
340
                }
341
            }
342
        } catch (ReflectionException $e) {
343
            //ignore
344
        }
345
        $parameters[] = $callback;
346
        call_user_func_array($callable, $parameters);
347
    }
348
349
    protected function _handleGenerator(Generator $flow, callable $callback, int $depth = 0)
350
    {
351
        $this->logGenerator($flow, $depth - 1);
352
        try {
353
            if (!$flow->valid()) {
354
                $callback(null, $flow->getReturn());
355
                if (!empty($flow->later)) {
356
                    $this->_awaitAll($flow->later, function ($error = null, $results = null) {
0 ignored issues
show
Bug introduced by
The property later does not seem to exist on Generator.
Loading history...
357
                    });
358
                    unset($flow->later);
359
                }
360
                return;
361
            }
362
            $value = $flow->current();
363
            $actions = $this->parse($flow->key() ?: Async::await);
364
            $next = function ($error = null, $result = null) use ($flow, $actions, $callback, $depth) {
365
                $value = $error ?: $result;
366
                if ($value instanceof Throwable) {
367
                    if (isset($actions['throw']) && is_a($value, $actions['throw'])) {
368
                        /** @scrutinizer ignore-call */
369
                        $flow->throw($value);
370
                        $this->_handleGenerator($flow, $callback, $depth);
371
                        return;
372
                    }
373
                    $callback($value, null);
374
                    return;
375
                }
376
                $flow->send($value);
377
                $this->_handleGenerator($flow, $callback, $depth);
378
            };
379
            if (key_exists(self::later, $actions)) {
380
                if (!isset($flow->later)) {
381
                    $flow->later = [];
382
                }
383
                if ($this->logger) {
384
                    $this->logger->info('later task scheduled', compact('depth'));
385
                }
386
                $flow->later[] = $value;
387
                return $next(null, $value);
388
            }
389
            if (key_exists(self::parallel, $actions)) {
390
                if (!isset($flow->parallel)) {
391
                    $flow->parallel = [];
0 ignored issues
show
Bug introduced by
The property parallel does not seem to exist on Generator.
Loading history...
392
                }
393
                $flow->parallel[] = $value;
394
                if (!isset($this->action)) {
395
                    $this->action = [];
0 ignored issues
show
Bug Best Practice introduced by
The property action does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
396
                }
397
                $this->action[] = self::parallel;
398
                return $next(null, $value);
399
            }
400
            if (key_exists(self::all, $actions)) {
401
                $tasks = Async::parallel === $value && isset($flow->parallel) ? $flow->parallel : $value;
402
                unset($flow->parallel);
403
                if (is_array($tasks) && count($tasks)) {
404
                    if ($this->logger) {
405
                        $this->logger->info(
406
                            sprintf("all {%d} tasks awaited.", count($tasks)),
407
                            compact('depth')
408
                        );
409
                    }
410
                    return $this->_awaitAll($tasks, $next);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->_awaitAll($tasks, $next) targeting LogicalSteps\Async\Async::_awaitAll() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
411
                }
412
                return $next(null, []);
413
            }
414
            $this->_handle($value, $next, $depth);
415
        } catch (Throwable $throwable) {
416
            $callback($throwable, null);
417
        }
418
    }
419
420
    /**
421
     * Handle known promise interfaces
422
     *
423
     * @param \React\Promise\PromiseInterface|\GuzzleHttp\Promise\PromiseInterface|\Amp\Promise|\Http\Promise\Promise $knownPromise
424
     * @param string $interface
425
     * @param callable $callback
426
     * @param int $depth
427
     * @return void
428
     */
429
    protected function _handlePromise($knownPromise, string $interface, callable $callback, int $depth = 0)
430
    {
431
        $this->logPromise($knownPromise, $interface, $depth);
432
        $resolver = function ($result) use ($callback) {
433
            $callback(null, $result);
434
        };
435
        $rejector = function ($error) use ($callback) {
436
            $callback($error, null);
437
        };
438
        try {
439
            switch ($interface) {
440
                case static::PROMISE_REACT:
441
                    $knownPromise->then($resolver, $rejector);
0 ignored issues
show
Bug introduced by
The method then() does not exist on Amp\Promise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

441
                    $knownPromise->/** @scrutinizer ignore-call */ 
442
                                   then($resolver, $rejector);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
442
                    break;
443
                case static::PROMISE_GUZZLE:
444
                    $knownPromise->then($resolver, $rejector);
445
                    if ($this->waitForGuzzleAndHttplug) {
446
                        if ($this->parallelGuzzleLoading) {
447
                            $this->guzzlePromises[] = $knownPromise;
448
                        } else {
449
                            $knownPromise->wait(false);
0 ignored issues
show
Bug introduced by
The method wait() does not exist on Amp\Promise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

449
                            $knownPromise->/** @scrutinizer ignore-call */ 
450
                                           wait(false);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method wait() does not exist on React\Promise\PromiseInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

449
                            $knownPromise->/** @scrutinizer ignore-call */ 
450
                                           wait(false);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
450
                        }
451
                    }
452
                    break;
453
                case static::PROMISE_HTTP:
454
                    $knownPromise->then($resolver, $rejector);
455
                    if ($this->waitForGuzzleAndHttplug) {
456
                        $knownPromise->wait(false);
457
                    }
458
                    break;
459
                case static::PROMISE_AMP:
460
                    $knownPromise->onResolve(
0 ignored issues
show
Bug introduced by
The method onResolve() does not exist on GuzzleHttp\Promise\PromiseInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

460
                    $knownPromise->/** @scrutinizer ignore-call */ 
461
                                   onResolve(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method onResolve() does not exist on Http\Promise\Promise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

460
                    $knownPromise->/** @scrutinizer ignore-call */ 
461
                                   onResolve(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method onResolve() does not exist on React\Promise\PromiseInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

460
                    $knownPromise->/** @scrutinizer ignore-call */ 
461
                                   onResolve(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
461
                        function ($error = null, $result = null) use ($resolver, $rejector) {
462
                            $error ? $rejector($error) : $resolver($result);
463
                        });
464
                    break;
465
            }
466
        } catch (\Exception $e) {
467
            $rejector($e);
468
        }
469
    }
470
471
    private function parse(string $command): array
472
    {
473
        $arr = [];
474
        if (strlen($command)) {
475
            parse_str(str_replace(['|', ':'], ['&', '='], $command), $arr);
476
        }
477
        return $arr;
478
    }
479
480
    private function action()
481
    {
482
        if (!empty($this->action)) {
483
            return array_shift($this->action);
484
        }
485
        return self::await;
486
    }
487
488
    private function logCallback(callable $callable, array $parameters, int $depth = 0)
489
    {
490
        if ($depth < 0 || !$this->logger) {
491
            return;
492
        }
493
        if (is_array($callable)) {
494
            $name = $callable[0];
495
            if (is_object($name)) {
496
                $name = '$' . lcfirst(get_class($name)) . '->' . $callable[1];
497
            } else {
498
                $name .= '::' . $callable[1];
499
            }
500
501
        } else {
502
            if (is_string($callable)) {
503
                $name = $callable;
504
            } elseif ($callable instanceof Closure) {
505
                $name = '$closure';
506
            } else {
507
                $name = '$callable';
508
            }
509
        }
510
        $this->logger->info(
511
            sprintf("%s %s%s", $this->action(), $name, $this->format($parameters)),
512
            compact('depth')
513
        );
514
    }
515
516
    private function logPromise(/** @scrutinizer ignore-unused */ $promise, string $interface, int $depth)
517
    {
518
        if ($depth < 0 || !$this->logger) {
519
            return;
520
        }
521
        $type = 'unknown';
522
        switch ($interface) {
523
            case static::PROMISE_REACT:
524
                $type = 'react';
525
                break;
526
            case static::PROMISE_GUZZLE:
527
                $type = 'guzzle';
528
                break;
529
            case static::PROMISE_HTTP:
530
                $type = 'httplug';
531
                break;
532
            case static::PROMISE_AMP:
533
                $type = 'amp';
534
                break;
535
        }
536
        $this->logger->info(
537
            sprintf("%s \$%sPromise;", $this->action(), $type),
538
            compact('depth')
539
        );
540
    }
541
542
    private function logGenerator(Generator $generator, int $depth = 0)
543
    {
544
        if ($depth < 0 || !$generator->valid() || !$this->logger) {
545
            return;
546
        }
547
        $info = new ReflectionGenerator($generator);
548
        $this->logReflectionFunction($info->getFunction(), $depth);
549
    }
550
551
    private function format($parameters)
552
    {
553
        return '(' . substr(json_encode($parameters), 1, -1) . ');';
554
    }
555
556
    private function logReflectionFunction(ReflectionFunctionAbstract $function, int $depth = 0)
557
    {
558
        if ($function instanceof ReflectionMethod) {
559
            $name = $function->getDeclaringClass()->getShortName();
560
            if ($function->isStatic()) {
561
                $name .= '::' . $function->name;
562
            } else {
563
                $name = '$' . lcfirst($name) . '->' . $function->name;
564
            }
565
        } elseif ($function->isClosure()) {
566
            $name = '$closure';
567
        } else {
568
            $name = $function->name;
569
        }
570
        $args = [];
571
        foreach ($function->getParameters() as $parameter) {
572
            $args[] = '$' . $parameter->name;
573
        }
574
        $this->logger->info(
575
            sprintf("%s %s(%s);", $this->action(), $name, implode(', ', $args)),
576
            compact('depth')
577
        );
578
    }
579
}
580