Passed
Push — master ( 6f92a7...802a51 )
by Arul
14:25
created

Async::_wait()   B

Complexity

Conditions 10
Paths 36

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
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, LoopInterface|Driver $loop = null) synchronously wait for the completion of an asynchronous process
25
 * @method mixed wait($process, LoopInterface|Driver $loop = null) 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 $param)
40
 * @method setLogger(LoggerInterface $param)
41
 */
42
class Async
43
{
44
    const PROMISE_REACT = 'React\Promise\PromiseInterface';
45
    const PROMISE_AMP = 'Amp\Promise';
46
    const PROMISE_GUZZLE = 'GuzzleHttp\Promise\PromiseInterface';
47
    const PROMISE_HTTP = 'Http\Promise\Promise';
48
49
    /** @var string action to return a promise instead of awaiting the response of the process. */
50
    const promise = 'promise';
51
    /** @var string action to run current process side by side with the remainder of the process. */
52
    const parallel = 'parallel';
53
    /** @var string action to await for all parallel processes previously to finish. */
54
    const all = 'all';
55
    /** @var string action to await for current  processes to finish. this is the default action. */
56
    const await = 'await';
57
    /** @var string action to run current process after finished executing the function. */
58
    const later = 'later';
59
60
    const ACTIONS = [self::await, self::parallel, self::all, self::promise, self::later];
61
62
    public static $knownPromises = [
63
        self::PROMISE_REACT,
64
        self::PROMISE_AMP,
65
        self::PROMISE_GUZZLE,
66
        self::PROMISE_HTTP,
67
    ];
68
69
    /**
70
     * @var LoggerInterface
71
     */
72
    protected $logger;
73
    /**
74
     * @var bool
75
     */
76
    public $waitForGuzzleAndHttplug = true;
77
78
    /**
79
     * @var bool
80
     */
81
    protected $parallelGuzzleLoading = false;
82
83
    protected $guzzlePromises = [];
84
85
    public function __construct(LoggerInterface $logger = null)
86
    {
87
        if ($logger) {
88
            $this->logger = $logger;
89
        }
90
    }
91
92
    public function __call($name, $arguments)
93
    {
94
        $value = null;
95
        $method = "_$name";
96
        switch ($name) {
97
            case 'await':
98
            case 'awaitAll':
99
                if ($this->logger) {
100
                    $this->logger->info('start');
101
                }
102
                if (1 == count($arguments)) {
103
                    //return promise
104
                    list($value, $resolver, $rejector) = $this->makePromise();
105
                    $callback = function ($error = null, $result = null) use ($resolver, $rejector) {
106
                        if ($this->logger) {
107
                            $this->logger->info('end');
108
                        }
109
                        if ($error) {
110
                            return $rejector($error);
111
                        }
112
                        return $resolver($result);
113
                    };
114
                    $arguments[1] = $callback;
115
                } elseif ($this->logger) {
116
                    $c = $arguments[1];
117
                    $callback = function ($error, $result = null) use ($c) {
118
                        $this->logger->info('end');
119
                        $c($error, $result);
120
                    };
121
                    $arguments[1] = $callback;
122
                }
123
                if ('await' == $name) {
124
                    $arguments[2] = -1;//depth
125
                    $method = '_handle';
126
                }
127
                break;
128
            case 'setLogger':
129
                break;
130
            case 'wait':
131
                return call_user_func_array([$this, $method], $arguments);
132
            default:
133
                return null;
134
        }
135
        call_user_func_array([$this, $method], $arguments);
136
        return $value;
137
    }
138
139
    public static function __callStatic($name, $arguments)
140
    {
141
        if (empty($arguments) && in_array($name, self::ACTIONS)) {
142
            return $name;
143
        }
144
        static $instance;
145
        if (!$instance) {
146
            $instance = new static();
147
        }
148
        return $instance->__call($name, $arguments);
149
    }
150
151
    /**
152
     * Throws specified or subclasses of specified exception inside the generator class so that it can be handled.
153
     *
154
     * @param string $throwable
155
     * @return string command
156
     *
157
     * @throws TypeError when given value is not a valid exception
158
     */
159
    public static function throw(string $throwable): string
160
    {
161
        if (is_a($throwable, Throwable::class, true)) {
162
            return __FUNCTION__ . ':' . $throwable;
163
        }
164
        throw new TypeError('Invalid value for throwable, it must extend Throwable class');
165
    }
166
167
    protected function _awaitAll(array $processes, callable $callback): void
168
    {
169
        $this->parallelGuzzleLoading = true;
170
        $results = [];
171
        $failed = false;
172
        foreach ($processes as $key => $process) {
173
            if ($failed)
174
                break;
175
            $c = function ($error = null, $result = null) use ($key, &$results, $processes, $callback, &$failed) {
176
                if ($failed)
177
                    return;
178
                if ($error) {
179
                    $failed = true;
180
                    $callback($error);
181
                    return;
182
                }
183
                $results[$key] = $result;
184
                if (count($results) == count($processes)) {
185
                    $callback(null, $results);
186
                }
187
            };
188
            $this->_handle($process, $c, -1);
189
        }
190
        if (!empty($this->guzzlePromises)) {
191
            guzzleAll($this->guzzlePromises)->wait(false);
192
            $this->guzzlePromises = [];
193
        }
194
        $this->parallelGuzzleLoading = false;
195
    }
196
197
    /**
198
     * Sets a logger instance on the object.
199
     *
200
     * @param LoggerInterface $logger
201
     *
202
     * @return void
203
     */
204
    protected function _setLogger(LoggerInterface $logger)
205
    {
206
        $this->logger = $logger;
207
    }
208
209
    private function makePromise()
210
    {
211
        $resolver = $rejector = null;
212
        $promise = new Promise(function ($resolve, $reject, $notify) use (&$resolver, &$rejector) {
213
            $resolver = $resolve;
214
            $rejector = $reject;
215
        });
216
        return [$promise, $resolver, $rejector];
217
    }
218
219
    protected function _wait($process, $loop = null)
220
    {
221
        if ($this->logger) {
222
            $this->logger->info('start');
223
        }
224
        $waiting = true;
225
        $result = null;
226
        $exception = null;
227
        $isRejected = false;
228
229
        $callback = function ($error = null, $r = null) use (&$result, &$waiting, &$isRejected, &$exception, $loop) {
230
            $waiting = false;
231
            if ($loop) {
232
                $loop->stop();
233
            }
234
            if ($error) {
235
                $isRejected = true;
236
                $exception = $error;
237
                return;
238
            }
239
            $result = $r;
240
        };
241
        $this->_handle($process, $callback, -1);
242
        while ($waiting) {
243
            if ($loop) {
244
                $loop->run();
245
            }
246
        }
247
        if ($this->logger) {
248
            $this->logger->info('end');
249
        }
250
        if ($isRejected) {
251
            if (!$exception instanceof \Exception) {
252
                $exception = new \UnexpectedValueException(
253
                    'process failed with ' . (is_object($exception) ? get_class($exception) : gettype($exception))
254
                );
255
            }
256
            throw $exception;
257
        }
258
259
        return $result;
260
    }
261
262
    protected function _handle($process, callable $callback, int $depth = 0): void
263
    {
264
        $arguments = [];
265
        $func = [];
266
        if (is_array($process) && count($process) > 1) {
267
            $copy = $process;
268
            $func[] = array_shift($copy);
269
            if (is_callable($func[0])) {
270
                $func = $func[0];
271
            } else {
272
                $func[] = array_shift($copy);
273
            }
274
            $arguments = $copy;
275
        } else {
276
            $func = $process;
277
        }
278
        if (is_callable($func)) {
279
            $this->_handleCallback($func, $arguments, $callback, $depth);
280
        } elseif ($process instanceof Generator) {
281
            $this->_handleGenerator($process, $callback, 1 + $depth);
282
        } elseif (is_object($process) && $implements = array_intersect(class_implements($process),
283
                Async::$knownPromises)) {
284
            $this->_handlePromise($process, array_shift($implements), $callback, $depth);
285
        } else {
286
            $callback(null, $process);
287
        }
288
    }
289
290
291
    protected function _handleCallback(callable $callable, array $parameters, callable $callback, int $depth = 0)
292
    {
293
        $this->logCallback($callable, $parameters, $depth);
294
        try {
295
            if (is_array($callable)) {
296
                $rf = new ReflectionMethod($callable[0], $callable[1]);
297
            } elseif (is_string($callable)) {
298
                $rf = new ReflectionFunction($callable);
299
            } 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

299
            } 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

299
            } elseif (is_a($callable, 'Closure') || is_callable($callable, /** @scrutinizer ignore-type */ '__invoke')) {
Loading history...
300
                $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

300
                $ro = new ReflectionObject(/** @scrutinizer ignore-type */ $callable);
Loading history...
301
                $rf = $ro->getMethod('__invoke');
302
            }
303
            $current = count($parameters);
304
            $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...
305
            $ps = $rf->getParameters();
306
            if ($current + 1 < $total) {
307
                for ($i = $current; $i < $total - 1; $i++) {
308
                    $parameters[$i] = $ps[$i]->isDefaultValueAvailable() ? $ps[$i]->getDefaultValue() : null;
309
                }
310
            }
311
        } catch (ReflectionException $e) {
312
            //ignore
313
        }
314
        $parameters[] = $callback;
315
        call_user_func_array($callable, $parameters);
316
    }
317
318
    protected function _handleGenerator(Generator $flow, callable $callback, int $depth = 0)
319
    {
320
        $this->logGenerator($flow, $depth - 1);
321
        try {
322
            if (!$flow->valid()) {
323
                $callback(null, $flow->getReturn());
324
                if (!empty($flow->later)) {
325
                    $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...
326
                    });
327
                    unset($flow->later);
328
                }
329
                return;
330
            }
331
            $value = $flow->current();
332
            $actions = $this->parse($flow->key() ?: Async::await);
333
            $next = function ($error = null, $result = null) use ($flow, $actions, $callback, $depth) {
334
                $value = $error ?: $result;
335
                if ($value instanceof Throwable) {
336
                    if (isset($actions['throw']) && is_a($value, $actions['throw'])) {
337
                        /** @scrutinizer ignore-call */
338
                        $flow->throw($value);
339
                        $this->_handleGenerator($flow, $callback, $depth);
340
                        return;
341
                    }
342
                    $callback($value, null);
343
                    return;
344
                }
345
                $flow->send($value);
346
                $this->_handleGenerator($flow, $callback, $depth);
347
            };
348
            if (key_exists(self::later, $actions)) {
349
                if (!isset($flow->later)) {
350
                    $flow->later = [];
351
                }
352
                if ($this->logger) {
353
                    $this->logger->info('later task scheduled', compact('depth'));
354
                }
355
                $flow->later[] = $value;
356
                return $next(null, $value);
357
            }
358
            if (key_exists(self::parallel, $actions)) {
359
                if (!isset($flow->parallel)) {
360
                    $flow->parallel = [];
0 ignored issues
show
Bug introduced by
The property parallel does not seem to exist on Generator.
Loading history...
361
                }
362
                $flow->parallel[] = $value;
363
                if (!isset($this->action)) {
364
                    $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...
365
                }
366
                $this->action[] = self::parallel;
367
                return $next(null, $value);
368
            }
369
            if (key_exists(self::all, $actions)) {
370
                $tasks = Async::parallel === $value && isset($flow->parallel) ? $flow->parallel : $value;
371
                unset($flow->parallel);
372
                if (is_array($tasks) && count($tasks)) {
373
                    if ($this->logger) {
374
                        $this->logger->info(
375
                            sprintf("all {%d} tasks awaited.", count($tasks)),
376
                            compact('depth')
377
                        );
378
                    }
379
                    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...
380
                }
381
                return $next(null, []);
382
            }
383
            $this->_handle($value, $next, $depth);
384
        } catch (Throwable $throwable) {
385
            $callback($throwable, null);
386
        }
387
    }
388
389
    /**
390
     * Handle known promise interfaces
391
     *
392
     * @param \React\Promise\PromiseInterface|\GuzzleHttp\Promise\PromiseInterface|\Amp\Promise|\Http\Promise\Promise $knownPromise
393
     * @param string $interface
394
     * @param callable $callback
395
     * @param int $depth
396
     * @return void
397
     */
398
    protected function _handlePromise($knownPromise, string $interface, callable $callback, int $depth = 0)
399
    {
400
        $this->logPromise($knownPromise, $interface, $depth);
401
        $resolver = function ($result) use ($callback) {
402
            $callback(null, $result);
403
        };
404
        $rejector = function ($error) use ($callback) {
405
            $callback($error, null);
406
        };
407
        try {
408
            switch ($interface) {
409
                case static::PROMISE_REACT:
410
                    $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

410
                    $knownPromise->/** @scrutinizer ignore-call */ 
411
                                   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...
411
                    break;
412
                case static::PROMISE_GUZZLE:
413
                    $knownPromise->then($resolver, $rejector);
414
                    if ($this->waitForGuzzleAndHttplug) {
415
                        if ($this->parallelGuzzleLoading) {
416
                            $this->guzzlePromises[] = $knownPromise;
417
                        } else {
418
                            $knownPromise->wait(false);
0 ignored issues
show
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

418
                            $knownPromise->/** @scrutinizer ignore-call */ 
419
                                           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 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

418
                            $knownPromise->/** @scrutinizer ignore-call */ 
419
                                           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...
419
                        }
420
                    }
421
                    break;
422
                case static::PROMISE_HTTP:
423
                    $knownPromise->then($resolver, $rejector);
424
                    if ($this->waitForGuzzleAndHttplug) {
425
                        $knownPromise->wait(false);
426
                    }
427
                    break;
428
                case static::PROMISE_AMP:
429
                    $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

429
                    $knownPromise->/** @scrutinizer ignore-call */ 
430
                                   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

429
                    $knownPromise->/** @scrutinizer ignore-call */ 
430
                                   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

429
                    $knownPromise->/** @scrutinizer ignore-call */ 
430
                                   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...
430
                        function ($error = null, $result = null) use ($resolver, $rejector) {
431
                            $error ? $rejector($error) : $resolver($result);
432
                        });
433
                    break;
434
            }
435
        } catch (\Exception $e) {
436
            $rejector($e);
437
        }
438
    }
439
440
    private function handleCommands(Generator $flow, &$value, callable $callback, int $depth): bool
441
    {
442
        $commands = $this->parse($flow->key());
443
        if ($value instanceof Throwable) {
444
            if (isset($commands['throw']) && is_a($value, $commands['throw'])) {
445
                $flow->throw($value);
0 ignored issues
show
Bug introduced by
The method throw() does not exist on Generator. ( Ignorable by Annotation )

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

445
                $flow->/** @scrutinizer ignore-call */ 
446
                       throw($value);

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...
446
                $this->_handleGenerator($flow, $callback, $depth);
447
                return true; //stop
448
            }
449
            $callback($value, null);
450
            return true; //stop
451
        }
452
        if (isset($commands[self::parallel])) {
453
            if (!isset($flow->parallel)) {
454
                $flow->parallel = [];
0 ignored issues
show
Bug introduced by
The property parallel does not seem to exist on Generator.
Loading history...
455
            }
456
            $flow->parallel [] = $value;
457
            return false; //continue
458
        }
459
460
        if (isset($commands[self::all])) {
461
            if (!isset($flow->parallel)) {
462
                $callback(null, []);
463
                return true; //stop
464
            }
465
            $this->_awaitAll(
466
                $flow->parallel,
467
                function ($error = null, $all = null) use ($flow, $callback, $depth) {
468
                    if ($error) {
469
                        $callback($error, false);
470
                        return;
471
                    }
472
                    $flow->send($all);
473
                    $this->_handleGenerator($flow, $callback, $depth);
474
                }
475
            );
476
            return true; //stop
477
        }
478
479
        return false; //continue
480
    }
481
482
    private function parse(string $command): array
483
    {
484
        $arr = [];
485
        if (strlen($command)) {
486
            parse_str(str_replace(['|', ':'], ['&', '='], $command), $arr);
487
        }
488
        return $arr;
489
    }
490
491
    private function action()
492
    {
493
        if (!empty($this->action)) {
494
            return array_shift($this->action);
495
        }
496
        return self::await;
497
    }
498
499
    private function logCallback(callable $callable, array $parameters, int $depth = 0)
500
    {
501
        if ($depth < 0 || !$this->logger) {
502
            return;
503
        }
504
        if (is_array($callable)) {
505
            $name = $callable[0];
506
            if (is_object($name)) {
507
                $name = '$' . lcfirst(get_class($name)) . '->' . $callable[1];
508
            } else {
509
                $name .= '::' . $callable[1];
510
            }
511
512
        } else {
513
            if (is_string($callable)) {
514
                $name = $callable;
515
            } elseif ($callable instanceof Closure) {
516
                $name = '$closure';
517
            } else {
518
                $name = '$callable';
519
            }
520
        }
521
        $this->logger->info(
522
            sprintf("%s %s%s", $this->action(), $name, $this->format($parameters)),
523
            compact('depth')
524
        );
525
    }
526
527
    private function logPromise(/** @scrutinizer ignore-unused */ $promise, string $interface, int $depth)
528
    {
529
        if ($depth < 0 || !$this->logger) {
530
            return;
531
        }
532
        $type = 'unknown';
533
        switch ($interface) {
534
            case static::PROMISE_REACT:
535
                $type = 'react';
536
                break;
537
            case static::PROMISE_GUZZLE:
538
                $type = 'guzzle';
539
                break;
540
            case static::PROMISE_HTTP:
541
                $type = 'httplug';
542
                break;
543
            case static::PROMISE_AMP:
544
                $type = 'amp';
545
                break;
546
        }
547
        $this->logger->info(
548
            sprintf("%s \$%sPromise;", $this->action(), $type),
549
            compact('depth')
550
        );
551
    }
552
553
    private function logGenerator(Generator $generator, int $depth = 0)
554
    {
555
        if ($depth < 0 || !$generator->valid() || !$this->logger) {
556
            return;
557
        }
558
        $info = new ReflectionGenerator($generator);
559
        $this->logReflectionFunction($info->getFunction(), $depth);
560
    }
561
562
    private function format($parameters)
563
    {
564
        return '(' . substr(json_encode($parameters), 1, -1) . ');';
565
    }
566
567
    private function logReflectionFunction(ReflectionFunctionAbstract $function, int $depth = 0)
568
    {
569
        if ($function instanceof ReflectionMethod) {
570
            $name = $function->getDeclaringClass()->getShortName();
571
            if ($function->isStatic()) {
572
                $name .= '::' . $function->name;
573
            } else {
574
                $name = '$' . lcfirst($name) . '->' . $function->name;
575
            }
576
        } elseif ($function->isClosure()) {
577
            $name = '$closure';
578
        } else {
579
            $name = $function->name;
580
        }
581
        $args = [];
582
        foreach ($function->getParameters() as $parameter) {
583
            $args[] = '$' . $parameter->name;
584
        }
585
        $this->logger->info(
586
            sprintf("%s %s(%s);", $this->action(), $name, implode(', ', $args)),
587
            compact('depth')
588
        );
589
    }
590
}
591