Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CallbackDescriber
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 3
11.48
0.00% covered (danger)
0.00%
0 / 1
 describe
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
6.05
 describeClosure
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 describeArrayCallable
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of fast-forward/defer.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/defer
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Defer\Support;
20
21use Closure;
22use ReflectionFunction;
23
24/**
25 * This utility class MUST be used to generate human-readable descriptions for any PHP callable.
26 * It SHALL support closures, array callables, invokable objects, and string callables.
27 * All methods MUST be static and MUST NOT throw exceptions.
28 */
29final class CallbackDescriber
30{
31    /**
32     * Returns a human-readable description for a callable.
33     *
34     * This method MUST support closures, array callables, invokable objects, and string callables.
35     *
36     * @param callable $callback the callable to describe
37     *
38     * @return string the description of the callable
39     */
40    public static function describe(callable $callback): string
41    {
42        if (\is_string($callback)) {
43            return $callback;
44        }
45
46        if ($callback instanceof Closure) {
47            return self::describeClosure($callback);
48        }
49
50        if (\is_array($callback)) {
51            return self::describeArrayCallable($callback);
52        }
53
54        if (\is_object($callback) && method_exists($callback, '__invoke')) {
55            return $callback::class . '::__invoke';
56        }
57
58        return 'callable';
59    }
60
61    /**
62     * Returns a description for a Closure, including file and line if available.
63     *
64     * @param Closure $closure the closure to describe
65     *
66     * @return string the description of the closure
67     */
68    private static function describeClosure(Closure $closure): string
69    {
70        $reflection = new ReflectionFunction($closure);
71        $file = $reflection->getFileName();
72        $line = $reflection->getStartLine();
73
74        if (false === $file || false === $line) {
75            return 'Closure';
76        }
77
78        return \sprintf('Closure@%s:%d', $file, $line);
79    }
80
81    /**
82     * Returns a description for an array callable.
83     *
84     * @param array{0: object|string, 1: string} $callback the array callable to describe
85     * @param array $callback
86     *
87     * @return string the description of the array callable
88     */
89    private static function describeArrayCallable(array $callback): string
90    {
91        [$target, $method] = $callback;
92
93        if (\is_object($target)) {
94            return $target::class . '->' . $method;
95        }
96
97        return $target . '::' . $method;
98    }
99}