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

nette / application / 19799648203

30 Nov 2025 01:31PM UTC coverage: 81.757% (+0.2%) from 81.549%
19799648203

push

github

dg
mockery dev [WIP]

1945 of 2379 relevant lines covered (81.76%)

0.82 hits per line

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

91.03
/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 string $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: ?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: 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: string}>
93
         */
94
        public function getPersistentComponents(): array
95
        {
96
                $class = $this->getName();
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
         * Is a method callable? It means class is instantiable and method has
120
         * public visibility, is non-static and non-abstract.
121
         */
122
        public function hasCallableMethod(string $method): bool
1✔
123
        {
124
                return $this->isInstantiable()
1✔
125
                        && $this->hasMethod($method)
1✔
126
                        && ($rm = $this->getMethod($method))
1✔
127
                        && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
1✔
128
        }
129

130

131
        /** Returns action*() or render*() method if available */
132
        public function getActionRenderMethod(string $action): ?\ReflectionMethod
1✔
133
        {
134
                $class = $this->name;
1✔
135
                return self::$armCache[$class][$action] ??=
1✔
136
                        $this->hasCallableMethod($name = $class::formatActionMethod($action))
1✔
137
                        || $this->hasCallableMethod($name = $class::formatRenderMethod($action))
1✔
138
                                ? parent::getMethod($name)
1✔
139
                                : null;
1✔
140
        }
141

142

143
        /** Returns handle*() method if available */
144
        public function getSignalMethod(string $signal): ?\ReflectionMethod
1✔
145
        {
146
                $class = $this->name;
1✔
147
                return $this->hasCallableMethod($name = $class::formatSignalMethod($signal))
1✔
148
                        ? parent::getMethod($name)
1✔
149
                        : null;
1✔
150
        }
151

152

153
        /**
154
         * Returns an annotation value.
155
         */
156
        public static function parseAnnotation(\Reflector $ref, string $name): ?array
1✔
157
        {
158
                if (!preg_match_all('#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#', (string) $ref->getDocComment(), $m)) {
1✔
159
                        return null;
1✔
160
                }
161

162
                $tokens = ['true' => true, 'false' => false, 'null' => null];
1✔
163
                $res = [];
1✔
164
                foreach ($m[1] as $s) {
1✔
165
                        foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
1✔
166
                                $res[] = array_key_exists($tmp = strtolower($item), $tokens)
1✔
167
                                        ? $tokens[$tmp]
1✔
168
                                        : $item;
1✔
169
                        }
170
                }
171

172
                return $res;
1✔
173
        }
174

175

176
        /**
177
         * Has class specified annotation?
178
         */
179
        public function hasAnnotation(string $name): bool
180
        {
181
                return (bool) self::parseAnnotation($this, $name);
×
182
        }
183

184

185
        /**
186
         * Returns an annotation value.
187
         */
188
        public function getAnnotation(string $name): mixed
189
        {
190
                $res = self::parseAnnotation($this, $name);
×
191
                return $res ? end($res) : null;
×
192
        }
193

194

195
        public function getMethod($name): MethodReflection
196
        {
197
                return new MethodReflection($this->getName(), $name);
1✔
198
        }
199

200

201
        /**
202
         * @return MethodReflection[]
203
         */
204
        public function getMethods($filter = -1): array
205
        {
206
                foreach ($res = parent::getMethods($filter) as $key => $val) {
×
207
                        $res[$key] = new MethodReflection($this->getName(), $val->getName());
×
208
                }
209

210
                return $res;
×
211
        }
212

213

214
        /** @deprecated  */
215
        public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array
216
        {
217
                return ParameterConverter::toArguments($method, $args);
×
218
        }
219
}
220

221

222
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