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

nette / application / 20368461488

19 Dec 2025 11:19AM UTC coverage: 82.434% (+0.7%) from 81.757%
20368461488

push

github

dg
improved phpDoc (#362)

1971 of 2391 relevant lines covered (82.43%)

0.82 hits per line

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

92.05
/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, class_exists, 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] or annotation @persistent.
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] or annotation @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
        public function getTemplateVariables(Control $control): array
1✔
119
        {
120
                $res = [];
1✔
121
                foreach ($this->getProperties() as $prop) {
1✔
122
                        if ($prop->getAttributes(Attributes\TemplateVariable::class)) {
1✔
123
                                $name = $prop->getName();
1✔
124
                                if (!$prop->isPublic()) {
1✔
125
                                        throw new \LogicException("Property {$this->getName()}::\$$name must be public to be used as TemplateVariable.");
1✔
126
                                } elseif ($prop->isInitialized($control) || (PHP_VERSION_ID >= 80400 && $prop->hasHook(\PropertyHookType::Get))) {
1✔
127
                                        $res[] = $name;
1✔
128
                                }
129
                        }
130
                }
131
                return $res;
1✔
132
        }
133

134

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

147

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

159

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

169

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

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

189
                return $res;
1✔
190
        }
191

192

193
        /**
194
         * Has class specified annotation?
195
         */
196
        public function hasAnnotation(string $name): bool
197
        {
198
                return (bool) self::parseAnnotation($this, $name);
×
199
        }
200

201

202
        /**
203
         * Returns an annotation value.
204
         */
205
        public function getAnnotation(string $name): mixed
206
        {
207
                $res = self::parseAnnotation($this, $name);
×
208
                return $res ? end($res) : null;
×
209
        }
210

211

212
        public function getMethod($name): MethodReflection
213
        {
214
                return new MethodReflection($this->getName(), $name);
1✔
215
        }
216

217

218
        /**
219
         * @return MethodReflection[]
220
         */
221
        public function getMethods($filter = -1): array
222
        {
223
                foreach ($res = parent::getMethods($filter) as $key => $val) {
×
224
                        $res[$key] = new MethodReflection($this->getName(), $val->getName());
×
225
                }
226

227
                return $res;
×
228
        }
229

230

231
        /** @deprecated  */
232
        public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array
233
        {
234
                return ParameterConverter::toArguments($method, $args);
×
235
        }
236
}
237

238

239
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