• 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

91.27
/src/Application/UI/Component.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;
11
use function array_key_exists, array_slice, class_exists, func_get_arg, func_get_args, func_num_args, get_debug_type, is_array, method_exists, sprintf, trigger_error;
12

13

14
/**
15
 * Component is the base class for all Presenter components.
16
 *
17
 * Components are persistent objects located on a presenter. They have ability to own
18
 * other child components, and interact with user. Components have properties
19
 * for storing their status, and responds to user command.
20
 *
21
 * @property-read Presenter $presenter
22
 * @property-read bool $linkCurrent
23
 * @implements \ArrayAccess<string, Nette\ComponentModel\IComponent>
24
 */
25
abstract class Component extends Nette\ComponentModel\Container implements SignalReceiver, StatePersistent, \ArrayAccess
26
{
27
        use Nette\ComponentModel\ArrayAccess;
28

29
        /** @var array<callable(static): void>  Occurs when component is attached to presenter */
30
        public array $onAnchor = [];
31

32
        /** @var array<string, mixed> */
33
        protected array $params = [];
34

35

36
        /**
37
         * Returns the presenter where this component belongs to. Throws if not attached.
38
         * @return ($throw is true ? Presenter : ?Presenter)
39
         */
40
        public function getPresenter(bool $throw = true): ?Presenter
1✔
41
        {
42
                return $this->lookup(Presenter::class, $throw);
1✔
43
        }
44

45

46
        /**
47
         * Returns the presenter where this component belongs to, or null if not attached.
48
         * @deprecated
49
         */
50
        public function getPresenterIfExists(): ?Presenter
51
        {
52
                return $this->lookup(Presenter::class, throw: false);
×
53
        }
54

55

56
        /** @deprecated */
57
        public function hasPresenter(): bool
58
        {
59
                return (bool) $this->lookup(Presenter::class, throw: false);
×
60
        }
61

62

63
        /**
64
         * Returns a fully-qualified name that uniquely identifies the component
65
         * within the presenter hierarchy.
66
         */
67
        public function getUniqueId(): string
68
        {
69
                return $this->lookupPath(Presenter::class);
1✔
70
        }
71

72

73
        protected function createComponent(string $name): ?Nette\ComponentModel\IComponent
1✔
74
        {
75
                if (method_exists($this, $method = 'createComponent' . $name)) {
1✔
76
                        (new AccessPolicy(new \ReflectionMethod($this, $method)))->checkAccess($this);
1✔
77
                }
78
                $res = parent::createComponent($name);
1✔
79
                if ($res && !$res instanceof SignalReceiver && !$res instanceof StatePersistent) {
1✔
80
                        $type = $res::class;
×
81
                        trigger_error("It seems that component '$name' of type $type is not intended to be used in the Presenter.");
×
82
                }
83

84
                return $res;
1✔
85
        }
86

87

88
        protected function validateParent(Nette\ComponentModel\IContainer $parent): void
1✔
89
        {
90
                parent::validateParent($parent);
1✔
91
                $this->monitor(Presenter::class, function (Presenter $presenter): void {
1✔
92
                        $this->loadState($presenter->popGlobalParameters($this->getUniqueId()));
1✔
93
                        Nette\Utils\Arrays::invoke($this->onAnchor, $this);
1✔
94
                });
1✔
95
        }
1✔
96

97

98
        /**
99
         * Calls public method if exists.
100
         * @param  array<string, mixed>  $params
101
         */
102
        protected function tryCall(string $method, array $params): bool
1✔
103
        {
104
                $rc = static::getReflection();
1✔
105
                if (!$rc->hasMethod($method)) {
1✔
106
                        return false;
1✔
107
                } elseif (!$rc->hasCallableMethod($method)) {
1✔
108
                        $this->error('Method ' . Nette\Utils\Reflection::toString($rc->getMethod($method)) . ' is not callable.');
×
109
                }
110

111
                $rm = $rc->getMethod($method);
1✔
112
                (new AccessPolicy($rm))->checkAccess($this);
1✔
113
                $this->checkRequirements($rm);
1✔
114
                try {
115
                        $args = ParameterConverter::toArguments($rm, $params);
1✔
116
                } catch (Nette\InvalidArgumentException $e) {
1✔
117
                        $this->error($e->getMessage());
1✔
118
                }
119

120
                $rm->invokeArgs($this, $args);
1✔
121
                return true;
1✔
122
        }
123

124

125
        /**
126
         * Descendant can override this method to check for permissions.
127
         * It is called with the presenter class and the render*(), action*(), and handle*() methods.
128
         * @param  \ReflectionClass<object>|\ReflectionMethod  $element
129
         */
130
        public function checkRequirements(\ReflectionClass|\ReflectionMethod $element): void
1✔
131
        {
132
        }
1✔
133

134

135
        /**
136
         * Access to reflection.
137
         */
138
        public static function getReflection(): ComponentReflection
139
        {
140
                return new ComponentReflection(static::class);
1✔
141
        }
142

143

144
        /********************* interface StatePersistent ****************d*g**/
145

146

147
        /**
148
         * Loads state information.
149
         * @param  array<string, mixed>  $params
150
         */
151
        public function loadState(array $params): void
1✔
152
        {
153
                $reflection = static::getReflection();
1✔
154
                foreach ($reflection->getParameters() as $name => $meta) {
1✔
155
                        if (isset($params[$name])) { // nulls are ignored
1✔
156
                                if (!ParameterConverter::convertType($params[$name], $meta['type'])) {
1✔
157
                                        $this->error(sprintf(
1✔
158
                                                "Value passed to persistent parameter '%s' in %s must be %s, %s given.",
1✔
159
                                                $name,
1✔
160
                                                $this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'",
1✔
161
                                                $meta['type'],
1✔
162
                                                get_debug_type($params[$name]),
1✔
163
                                        ));
164
                                }
165

166
                                $this->$name = $params[$name];
1✔
167
                        } else {
168
                                $params[$name] = $this->$name ?? null;
1✔
169
                        }
170
                }
171

172
                $this->params = $params;
1✔
173
        }
1✔
174

175

176
        /**
177
         * Saves state information for next request.
178
         * @param  array<string, mixed>  $params
179
         */
180
        public function saveState(array &$params): void
1✔
181
        {
182
                $this->saveStatePartial($params, static::getReflection());
1✔
183
        }
1✔
184

185

186
        /**
187
         * @internal used by presenter
188
         * @param  array<string, mixed>  $params
189
         */
190
        public function saveStatePartial(array &$params, ComponentReflection $reflection): void
1✔
191
        {
192
                $tree = Nette\Application\Helpers::getClassesAndTraits(static::class);
1✔
193

194
                foreach ($reflection->getPersistentParams() as $name => $meta) {
1✔
195
                        if (isset($params[$name])) {
1✔
196
                                // injected value
197

198
                        } elseif (
199
                                array_key_exists($name, $params) // nulls are skipped
1✔
200
                                || (isset($meta['since']) && !isset($tree[$meta['since']])) // not related
1✔
201
                                || !isset($this->$name)
1✔
202
                        ) {
203
                                continue;
1✔
204

205
                        } else {
206
                                $params[$name] = $this->$name; // object property value
1✔
207
                        }
208

209
                        if (!ParameterConverter::convertType($params[$name], $meta['type'])) {
1✔
210
                                throw new InvalidLinkException(sprintf(
1✔
211
                                        "Value passed to persistent parameter '%s' in %s must be %s, %s given.",
1✔
212
                                        $name,
1✔
213
                                        $this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'",
1✔
214
                                        $meta['type'],
1✔
215
                                        get_debug_type($params[$name]),
1✔
216
                                ));
217
                        }
218

219
                        if ($params[$name] === $meta['def'] || ($meta['def'] === null && $params[$name] === '')) {
1✔
220
                                $params[$name] = null; // value transmit is unnecessary
1✔
221
                        }
222
                }
223
        }
1✔
224

225

226
        /**
227
         * Returns component param.
228
         */
229
        final public function getParameter(string $name): mixed
1✔
230
        {
231
                if (func_num_args() > 1) {
1✔
232
                        $default = func_get_arg(1);
×
233
                }
234
                return $this->params[$name] ?? $default ?? null;
1✔
235
        }
236

237

238
        /**
239
         * Returns component parameters.
240
         * @return array<string, mixed>
241
         */
242
        final public function getParameters(): array
243
        {
244
                return $this->params;
1✔
245
        }
246

247

248
        /**
249
         * Returns a fully-qualified name that uniquely identifies the parameter.
250
         */
251
        final public function getParameterId(string $name): string
1✔
252
        {
253
                $uid = $this->getUniqueId();
1✔
254
                return $uid === '' ? $name : $uid . self::NameSeparator . $name;
1✔
255
        }
256

257

258
        /********************* interface SignalReceiver ****************d*g**/
259

260

261
        /**
262
         * Calls signal handler method.
263
         * @throws BadSignalException if there is not handler method
264
         */
265
        public function signalReceived(string $signal): void
1✔
266
        {
267
                if (!$this->tryCall(static::formatSignalMethod($signal), $this->params)) {
1✔
268
                        $class = static::class;
1✔
269
                        throw new BadSignalException("There is no handler for signal '$signal' in class $class.");
1✔
270
                }
271
        }
1✔
272

273

274
        /**
275
         * Formats signal handler method name -> case sensitivity doesn't matter.
276
         */
277
        public static function formatSignalMethod(string $signal): string
1✔
278
        {
279
                return 'handle' . $signal;
1✔
280
        }
281

282

283
        /********************* navigation ****************d*g**/
284

285

286
        /**
287
         * Generates URL to presenter, action or signal.
288
         * @param  string   $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
289
         * @param  array|mixed  $args
290
         * @throws InvalidLinkException
291
         */
292
        public function link(string $destination, $args = []): string
1✔
293
        {
294
                try {
295
                        $args = func_num_args() < 3 && is_array($args)
1✔
296
                                ? $args
1✔
297
                                : array_slice(func_get_args(), 1);
1✔
298
                        return $this->getPresenter()->getLinkGenerator()->link($destination, $args, $this, 'link');
1✔
299

300
                } catch (InvalidLinkException $e) {
1✔
301
                        return $this->getPresenter()->handleInvalidLink($e);
1✔
302
                }
303
        }
304

305

306
        /**
307
         * Returns destination as Link object.
308
         * @param  string   $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
309
         * @param  array|mixed  $args
310
         */
311
        public function lazyLink(string $destination, $args = []): Link
312
        {
313
                $args = func_num_args() < 3 && is_array($args)
×
314
                        ? $args
×
315
                        : array_slice(func_get_args(), 1);
×
316
                return new Link($this, $destination, $args);
×
317
        }
318

319

320
        /**
321
         * Determines whether it links to the current page.
322
         * @param  ?string   $destination in format "[[[module:]presenter:]action | signal! | this]"
323
         * @param  array|mixed  $args
324
         * @throws InvalidLinkException
325
         */
326
        public function isLinkCurrent(?string $destination = null, $args = []): bool
1✔
327
        {
328
                if ($destination !== null) {
1✔
329
                        $args = func_num_args() < 3 && is_array($args)
1✔
330
                                ? $args
1✔
331
                                : array_slice(func_get_args(), 1);
×
332
                        $this->getPresenter()->getLinkGenerator()->createRequest($this, $destination, $args, 'test');
1✔
333
                }
334

335
                return $this->getPresenter()->getLastCreatedRequestFlag('current');
1✔
336
        }
337

338

339
        /**
340
         * Redirect to another presenter, action or signal.
341
         * @param  string   $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
342
         * @param  array|mixed  $args
343
         * @return never
344
         * @throws Nette\Application\AbortException
345
         */
346
        public function redirect(string $destination, $args = []): void
1✔
347
        {
348
                $args = func_num_args() < 3 && is_array($args)
1✔
349
                        ? $args
1✔
350
                        : array_slice(func_get_args(), 1);
1✔
351
                $presenter = $this->getPresenter();
1✔
352
                $presenter->saveGlobalState();
1✔
353
                $presenter->redirectUrl($presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect'));
1✔
354
        }
355

356

357
        /**
358
         * Permanently redirects to presenter, action or signal.
359
         * @param  string   $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
360
         * @param  array|mixed  $args
361
         * @return never
362
         * @throws Nette\Application\AbortException
363
         */
364
        public function redirectPermanent(string $destination, $args = []): void
1✔
365
        {
366
                $args = func_num_args() < 3 && is_array($args)
1✔
367
                        ? $args
1✔
368
                        : array_slice(func_get_args(), 1);
1✔
369
                $presenter = $this->getPresenter();
1✔
370
                $presenter->redirectUrl(
1✔
371
                        $presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect'),
1✔
372
                        Nette\Http\IResponse::S301_MovedPermanently,
1✔
373
                );
374
        }
375

376

377
        /**
378
         * Throws HTTP error.
379
         * @throws Nette\Application\BadRequestException
380
         * @return never
381
         */
382
        public function error(string $message = '', int $httpCode = Nette\Http\IResponse::S404_NotFound): void
1✔
383
        {
384
                throw new Nette\Application\BadRequestException($message, $httpCode);
1✔
385
        }
386
}
387

388

389
class_exists(PresenterComponent::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