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

nette / utils / 21934836766

12 Feb 2026 05:29AM UTC coverage: 93.429% (+0.01%) from 93.415%
21934836766

push

github

dg
added CLAUDE.md

2076 of 2222 relevant lines covered (93.43%)

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  callable-string  $function
26
         * @param  list<mixed>  $args
27
         * @param  callable(string, int): (bool|void|null)  $onError
28
         */
29
        public static function invokeSafe(string $function, array $args, callable $onError): mixed
1✔
30
        {
31
                $prev = set_error_handler(function (int $severity, string $message, string $file, int $line) use ($onError, &$prev, $function): ?bool {
1✔
32
                        if ($file === __FILE__) {
1✔
33
                                $msg = ini_get('html_errors')
1✔
34
                                        ? Html::htmlToText($message)
35
                                        : $message;
1✔
36
                                $msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
1✔
37
                                if ($onError($msg, $severity) !== false) {
1✔
38
                                        return null;
1✔
39
                                }
40
                        }
41

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

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

52

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

69
                return $callable;
1✔
70
        }
71

72

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

87

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

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

110

111
        /**
112
         * Checks whether PHP callback is function or static method.
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
         * @return \Closure|array{object|class-string, string}|callable-string
123
         */
124
        public static function unwrap(\Closure $closure): callable|array
1✔
125
        {
126
                $r = new \ReflectionFunction($closure);
1✔
127
                $class = $r->getClosureScopeClass()?->name;
1✔
128
                if (str_ends_with($r->name, '}')) {
1✔
129
                        return $closure;
1✔
130

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

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

137
                } else {
138
                        return $r->name;
1✔
139
                }
140
        }
141
}
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