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

nette / application / 20921730768

12 Jan 2026 01:49PM UTC coverage: 84.059% (+0.02%) from 84.039%
20921730768

push

github

dg
normalized callable to Closure

5 of 5 new or added lines in 3 files covered. (100.0%)

134 existing lines in 9 files now uncovered.

2009 of 2390 relevant lines covered (84.06%)

0.84 hits per line

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

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

15

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

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

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

36

37
        /**
38
         * Returns the presenter where this component belongs to.
39
         */
40
        public function getPresenter(): ?Presenter
41
        {
42
                if (func_num_args()) {
1✔
UNCOV
43
                        trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getPresenterIfExists()', E_USER_DEPRECATED);
×
UNCOV
44
                        $throw = func_get_arg(0);
×
45
                }
46

47
                return $this->lookup(Presenter::class, throw: $throw ?? true);
1✔
48
        }
49

50

51
        /**
52
         * Returns the presenter where this component belongs to.
53
         */
54
        public function getPresenterIfExists(): ?Presenter
55
        {
56
                return $this->lookup(Presenter::class, throw: false);
1✔
57
        }
58

59

60
        /** @deprecated */
61
        public function hasPresenter(): bool
62
        {
UNCOV
63
                return (bool) $this->lookup(Presenter::class, throw: false);
×
64
        }
65

66

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

76

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

88
                return $res;
1✔
89
        }
90

91

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

101

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

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

124
                $rm->invokeArgs($this, $args);
1✔
125
                return true;
1✔
126
        }
127

128

129
        /**
130
         * Descendant can override this method to check for permissions.
131
         * It is called with the presenter class and the render*(), action*(), and handle*() methods.
132
         */
133
        public function checkRequirements(\ReflectionClass|\ReflectionMethod $element): void
1✔
134
        {
135
        }
1✔
136

137

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

146

147
        /********************* interface StatePersistent ****************d*g**/
148

149

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

169
                                $this->$name = $params[$name];
1✔
170
                        } else {
171
                                $params[$name] = $this->$name ?? null;
1✔
172
                        }
173
                }
174

175
                $this->params = $params;
1✔
176
        }
1✔
177

178

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

188

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

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

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

208
                        } else {
209
                                $params[$name] = $this->$name; // object property value
1✔
210
                        }
211

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

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

228

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

240

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

250

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

260

261
        /********************* interface SignalReceiver ****************d*g**/
262

263

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

276

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

285

286
        /********************* navigation ****************d*g**/
287

288

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

303
                } catch (InvalidLinkException $e) {
1✔
304
                        return $this->getPresenter()->handleInvalidLink($e);
1✔
305
                }
306
        }
307

308

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

322

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

338
                return $this->getPresenter()->getLastCreatedRequestFlag('current');
1✔
339
        }
340

341

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

359

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

379

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

390

391
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