• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / utils / 21636962765

03 Feb 2026 03:39PM UTC coverage: 93.312% (+0.05%) from 93.264%
21636962765

push

github

dg
added CLAUDE.md

2065 of 2213 relevant lines covered (93.31%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/src/Utils/Callback.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Utils;
11

12
use Nette;
13
use function explode, func_get_args, ini_get, is_array, is_callable, is_object, is_string, preg_replace, restore_error_handler, set_error_handler, sprintf, str_contains, str_ends_with;
14

15

16
/**
17
 * PHP callable tools.
18
 */
19
final class Callback
20
{
21
        use Nette\StaticClass;
22

23
        /**
24
         * Invokes internal PHP function with own error handler.
25
         * @param  list<mixed>  $args
26
         * @param  callable(string, int): ?bool  $onError
27
         */
28
        public static function invokeSafe(string $function, array $args, callable $onError): mixed
1✔
29
        {
30
                $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
1✔
31
                        if ($file === __FILE__) {
1✔
32
                                $msg = ini_get('html_errors')
1✔
33
                                        ? Html::htmlToText($message)
34
                                        : $message;
1✔
35
                                $msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
1✔
36
                                if ($onError($msg, $severity) !== false) {
1✔
37
                                        return null;
1✔
38
                                }
39
                        }
40

41
                        return $prev ? $prev(...func_get_args()) : false;
1✔
42
                });
1✔
43

44
                try {
45
                        return $function(...$args);
1✔
46
                } finally {
×
47
                        restore_error_handler();
1✔
48
                }
49
        }
50

51

52
        /**
53
         * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
54
         * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
55
         * @return callable(): mixed
56
         * @throws Nette\InvalidArgumentException
57
         */
58
        public static function check(mixed $callable, bool $syntax = false): mixed
1✔
59
        {
60
                if (!is_callable($callable, $syntax)) {
1✔
61
                        throw new Nette\InvalidArgumentException(
1✔
62
                                $syntax
1✔
63
                                ? 'Given value is not a callable type.'
1✔
64
                                : sprintf("Callback '%s' is not callable.", self::toString($callable)),
1✔
65
                        );
66
                }
67

68
                return $callable;
1✔
69
        }
70

71

72
        /**
73
         * Converts PHP callback to textual form. Class or method may not exists.
74
         */
75
        public static function toString(mixed $callable): string
1✔
76
        {
77
                if ($callable instanceof \Closure) {
1✔
78
                        $inner = self::unwrap($callable);
1✔
79
                        return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
1✔
80
                } else {
81
                        is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
1✔
82
                        return $textual;
1✔
83
                }
84
        }
85

86

87
        /**
88
         * Returns reflection for method or function used in PHP callback.
89
         * @param  callable(): mixed  $callable  type check is escalated to ReflectionException
90
         * @throws \ReflectionException  if callback is not valid
91
         */
92
        public static function toReflection($callable): \ReflectionMethod|\ReflectionFunction
93
        {
94
                if ($callable instanceof \Closure) {
1✔
95
                        $callable = self::unwrap($callable);
1✔
96
                }
97

98
                if (is_string($callable) && str_contains($callable, '::')) {
1✔
99
                        return new ReflectionMethod(...explode('::', $callable, 2));
1✔
100
                } elseif (is_array($callable)) {
1✔
101
                        return new ReflectionMethod($callable[0], $callable[1]);
1✔
102
                } elseif (is_object($callable) && !$callable instanceof \Closure) {
1✔
103
                        return new ReflectionMethod($callable, '__invoke');
1✔
104
                } else {
105
                        return new \ReflectionFunction($callable);
1✔
106
                }
107
        }
108

109

110
        /**
111
         * Checks whether PHP callback is function or static method.
112
         * @param  callable(): mixed  $callable
113
         */
114
        public static function isStatic(callable $callable): bool
115
        {
116
                return is_string(is_array($callable) ? $callable[0] : $callable);
×
117
        }
118

119

120
        /**
121
         * Unwraps closure created by Closure::fromCallable().
122
         * @param  \Closure(): mixed  $closure
123
         * @return \Closure|array{class-string|object, string}|string
124
         */
125
        public static function unwrap(\Closure $closure): \Closure|array|string
1✔
126
        {
127
                $r = new \ReflectionFunction($closure);
1✔
128
                $class = $r->getClosureScopeClass()?->name;
1✔
129
                if (str_ends_with($r->name, '}')) {
1✔
130
                        return $closure;
1✔
131

132
                } elseif (($obj = $r->getClosureThis()) && $obj::class === $class) {
1✔
133
                        return [$obj, $r->name];
1✔
134

135
                } elseif ($class) {
1✔
136
                        return [$class, $r->name];
1✔
137

138
                } else {
139
                        return $r->name;
1✔
140
                }
141
        }
142
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc