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

nette / application / 20834855301

08 Jan 2026 10:54PM UTC coverage: 84.605% (+1.7%) from 82.856%
20834855301

push

github

dg
added CLAUDE.md

1940 of 2293 relevant lines covered (84.61%)

0.85 hits per line

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

90.32
/src/Application/UI/ComponentReflection.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\Application\UI;
11

12
use Nette\Application\Attributes;
13
use Nette\Utils\Reflection;
14
use function array_fill_keys, array_filter, array_key_exists, array_merge, end, preg_match_all, preg_quote, preg_split, strtolower;
15
use const PREG_SPLIT_NO_EMPTY;
16

17

18
/**
19
 * Helpers for Presenter & Component.
20
 * @property-read class-string<Control> $name
21
 * @property-read string $fileName
22
 * @internal
23
 */
24
final class ComponentReflection extends \ReflectionClass
25
{
26
        private static array $ppCache = [];
27
        private static array $pcCache = [];
28
        private static array $armCache = [];
29

30

31
        /**
32
         * Returns array of class properties that are public and have attribute #[Persistent] or #[Parameter].
33
         * @return array<string, array{def: mixed, type: string, since: ?class-string}>
34
         */
35
        public function getParameters(): array
36
        {
37
                $params = &self::$ppCache[$this->getName()];
1✔
38
                if ($params !== null) {
1✔
39
                        return $params;
1✔
40
                }
41

42
                $params = [];
1✔
43
                $isPresenter = $this->isSubclassOf(Presenter::class);
1✔
44
                foreach ($this->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
1✔
45
                        if ($prop->isStatic()) {
1✔
46
                                continue;
1✔
47
                        } elseif (
48
                                self::parseAnnotation($prop, 'persistent')
1✔
49
                                || $prop->getAttributes(Attributes\Persistent::class)
1✔
50
                        ) {
51
                                $params[$prop->getName()] = [
1✔
52
                                        'def' => $prop->hasDefaultValue() ? $prop->getDefaultValue() : null,
1✔
53
                                        'type' => ParameterConverter::getType($prop),
1✔
54
                                        'since' => $isPresenter ? Reflection::getPropertyDeclaringClass($prop)->getName() : null,
1✔
55
                                ];
56
                        } elseif ($prop->getAttributes(Attributes\Parameter::class)) {
1✔
57
                                $params[$prop->getName()] = [
1✔
58
                                        'def' => $prop->hasDefaultValue() ? $prop->getDefaultValue() : null,
1✔
59
                                        'type' => (string) ($prop->getType() ?? 'mixed'),
1✔
60
                                ];
61
                        }
62
                }
63

64
                if ($this->getParentClass()->isSubclassOf(Component::class)) {
1✔
65
                        $parent = new self($this->getParentClass()->getName());
1✔
66
                        foreach ($parent->getParameters() as $name => $meta) {
1✔
67
                                if (!isset($params[$name])) {
1✔
68
                                        $params[$name] = $meta;
1✔
69
                                } elseif (array_key_exists('since', $params[$name])) {
1✔
70
                                        $params[$name]['since'] = $meta['since'];
1✔
71
                                }
72
                        }
73
                }
74

75
                return $params;
1✔
76
        }
77

78

79
        /**
80
         * Returns array of persistent properties. They are public and have attribute #[Persistent].
81
         * @return array<string, array{def: mixed, type: string, since: class-string}>
82
         */
83
        public function getPersistentParams(): array
84
        {
85
                return array_filter($this->getParameters(), fn($param) => array_key_exists('since', $param));
1✔
86
        }
87

88

89
        /**
90
         * Returns array of persistent components. They are tagged with class-level attribute
91
         * #[Persistent] or annotation @persistent or returned by Presenter::getPersistentComponents().
92
         * @return array<string, array{since: class-string}>
93
         */
94
        public function getPersistentComponents(): array
95
        {
96
                $class = $this->name;
1✔
97
                $components = &self::$pcCache[$class];
1✔
98
                if ($components !== null) {
1✔
99
                        return $components;
1✔
100
                }
101

102
                $attrs = $this->getAttributes(Attributes\Persistent::class);
1✔
103
                $names = $attrs
1✔
104
                        ? $attrs[0]->getArguments()
1✔
105
                        : (array) self::parseAnnotation($this, 'persistent');
1✔
106
                $names = array_merge($names, $class::getPersistentComponents());
1✔
107
                $components = array_fill_keys($names, ['since' => $class]);
1✔
108

109
                if ($this->isSubclassOf(Presenter::class)) {
1✔
110
                        $parent = new self($this->getParentClass()->getName());
1✔
111
                        $components = $parent->getPersistentComponents() + $components;
1✔
112
                }
113

114
                return $components;
1✔
115
        }
116

117

118
        /**
119
         * @return string[] names of public properties with #[TemplateVariable] attribute
120
         */
121
        public function getTemplateVariables(Control $control): array
1✔
122
        {
123
                $res = [];
1✔
124
                foreach ($this->getProperties() as $prop) {
1✔
125
                        if ($prop->getAttributes(Attributes\TemplateVariable::class)) {
1✔
126
                                $name = $prop->getName();
1✔
127
                                if (!$prop->isPublic()) {
1✔
128
                                        throw new \LogicException("Property {$this->getName()}::\$$name must be public to be used as TemplateVariable.");
1✔
129
                                } elseif ($prop->isInitialized($control) || (PHP_VERSION_ID >= 80400 && $prop->hasHook(\PropertyHookType::Get))) {
1✔
130
                                        $res[] = $name;
1✔
131
                                }
132
                        }
133
                }
134
                return $res;
1✔
135
        }
136

137

138
        /**
139
         * Is a method callable? It means class is instantiable and method has
140
         * public visibility, is non-static and non-abstract.
141
         */
142
        public function hasCallableMethod(string $method): bool
1✔
143
        {
144
                return $this->isInstantiable()
1✔
145
                        && $this->hasMethod($method)
1✔
146
                        && ($rm = $this->getMethod($method))
1✔
147
                        && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
1✔
148
        }
149

150

151
        /** Returns action*() or render*() method if available */
152
        public function getActionRenderMethod(string $action): ?\ReflectionMethod
1✔
153
        {
154
                $class = $this->name;
1✔
155
                return self::$armCache[$class][$action] ??=
1✔
156
                        $this->hasCallableMethod($name = $class::formatActionMethod($action))
1✔
157
                        || $this->hasCallableMethod($name = $class::formatRenderMethod($action))
1✔
158
                                ? parent::getMethod($name)
1✔
159
                                : null;
1✔
160
        }
161

162

163
        /** Returns handle*() method if available */
164
        public function getSignalMethod(string $signal): ?\ReflectionMethod
1✔
165
        {
166
                $class = $this->name;
1✔
167
                return $this->hasCallableMethod($name = $class::formatSignalMethod($signal))
1✔
168
                        ? parent::getMethod($name)
1✔
169
                        : null;
1✔
170
        }
171

172

173
        /**
174
         * Returns an annotation value.
175
         * @deprecated
176
         */
177
        public static function parseAnnotation(\Reflector $ref, string $name): ?array
1✔
178
        {
179
                if (!preg_match_all('#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#', (string) $ref->getDocComment(), $m)) {
1✔
180
                        return null;
1✔
181
                }
182

183
                $tokens = ['true' => true, 'false' => false, 'null' => null];
1✔
184
                $res = [];
1✔
185
                foreach ($m[1] as $s) {
1✔
186
                        foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
1✔
187
                                $res[] = array_key_exists($tmp = strtolower($item), $tokens)
1✔
188
                                        ? $tokens[$tmp]
1✔
189
                                        : $item;
1✔
190
                        }
191
                }
192

193
                $alt = match ($name) {
1✔
194
                        'persistent' => '#[Nette\Application\Attributes\Persistent]',
1✔
195
                        'deprecated' => '#[Nette\Application\Attributes\Deprecated]',
×
196
                        'crossOrigin' => '#[Nette\Application\Attributes\Request(sameOrigin: false)]',
×
197
                        default => 'alternative'
1✔
198
                };
199
                trigger_error("Annotation @$name is deprecated, use $alt (used in " . Reflection::toString($ref) . ')', E_USER_DEPRECATED);
1✔
200
                return $res;
1✔
201
        }
202

203

204
        #[\Deprecated]
205
        public function hasAnnotation(string $name): bool
206
        {
207
                return (bool) self::parseAnnotation($this, $name);
×
208
        }
209

210

211
        #[\Deprecated]
212
        public function getAnnotation(string $name): mixed
213
        {
214
                $res = self::parseAnnotation($this, $name);
×
215
                return $res ? end($res) : null;
×
216
        }
217

218

219
        public function getMethod($name): MethodReflection
220
        {
221
                return new MethodReflection($this->getName(), $name);
1✔
222
        }
223

224

225
        /**
226
         * @return MethodReflection[]
227
         */
228
        public function getMethods($filter = -1): array
229
        {
230
                foreach ($res = parent::getMethods($filter) as $key => $val) {
×
231
                        $res[$key] = new MethodReflection($this->getName(), $val->getName());
×
232
                }
233

234
                return $res;
×
235
        }
236

237

238
        #[\Deprecated]
239
        public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array
240
        {
241
                return ParameterConverter::toArguments($method, $args);
×
242
        }
243
}
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