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

nette / application / 27919019709

21 Jun 2026 10:08PM UTC coverage: 84.111% (+0.05%) from 84.059%
27919019709

push

github

dg
phpstan fix

2038 of 2423 relevant lines covered (84.11%)

0.84 hits per line

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

92.13
/src/Application/UI/ComponentReflection.php
1
<?php declare(strict_types=1);
1✔
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
namespace Nette\Application\UI;
9

10
use Nette\Application\Attributes;
11
use Nette\Utils\Reflection;
12
use function array_fill_keys, array_filter, array_key_exists, array_merge, class_exists, end, preg_match_all, preg_quote, preg_split, strtolower;
13
use const PHP_VERSION_ID;
14

15

16
/**
17
 * Helpers for Presenter & Component.
18
 * @extends \ReflectionClass<object>
19
 * @property-read class-string<Control> $name
20
 * @property-read string $fileName
21
 * @internal
22
 */
23
final class ComponentReflection extends \ReflectionClass
24
{
25
        /** @var array<string, array<string, array{def: mixed, type: string, since?: ?class-string}>> */
26
        private static array $ppCache = [];
27

28
        /** @var array<string, array<string, array{since: class-string}>> */
29
        private static array $pcCache = [];
30

31
        /** @var array<string, array<string, ?\ReflectionMethod>> */
32
        private static array $armCache = [];
33

34

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

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

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

79
                return $params;
1✔
80
        }
81

82

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

92

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

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

113
                if ($this->isSubclassOf(Presenter::class)) {
1✔
114
                        $parent = new self($this->getParentClass()->getName());
1✔
115
                        $components = $parent->getPersistentComponents() + $components;
1✔
116
                }
117

118
                return $components;
1✔
119
        }
120

121

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

141

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

154

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

166

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

176

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

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

197
                return $res;
1✔
198
        }
199

200

201
        /**
202
         * Has class specified annotation?
203
         */
204
        public function hasAnnotation(string $name): bool
205
        {
206
                return (bool) self::parseAnnotation($this, $name);
×
207
        }
208

209

210
        /**
211
         * Returns an annotation value.
212
         */
213
        public function getAnnotation(string $name): mixed
214
        {
215
                $res = self::parseAnnotation($this, $name);
×
216
                return $res ? end($res) : null;
×
217
        }
218

219

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

225

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

235
                return $res;
×
236
        }
237

238

239
        /**
240
         * @deprecated
241
         * @param  array<string, mixed>  $args
242
         * @return list<mixed>
243
         */
244
        public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array
245
        {
246
                return ParameterConverter::toArguments($method, $args);
×
247
        }
248
}
249

250

251
class_exists(PresenterComponentReflection::class);
1✔
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