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

nette / application / 28331702164

28 Jun 2026 06:23PM UTC coverage: 84.573% (-0.2%) from 84.766%
28331702164

push

github

dg
added CLAUDE.md

2001 of 2366 relevant lines covered (84.57%)

0.85 hits per line

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

89.39
/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, 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-deprecated Presenter $presenter
22
 * @property-deprecated 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\SmartObject;
28
        use Nette\ComponentModel\ArrayAccess;
29

30
        /** @var array<callable(static): 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. Throws if not attached.
39
         * @return ($throw is true ? Presenter : ?Presenter)
40
         */
41
        public function getPresenter(bool $throw = true): ?Presenter
1✔
42
        {
43
                return $this->lookup(Presenter::class, $throw);
1✔
44
        }
45

46

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

56

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

63

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

73

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

85
                return $res;
1✔
86
        }
87

88

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

98

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

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

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

125

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

135

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

144

145
        /********************* interface StatePersistent ****************d*g**/
146

147

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

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

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

176

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

186

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

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

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

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

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

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

226

227
        /**
228
         * Returns component param.
229
         */
230
        final public function getParameter(string $name): mixed
1✔
231
        {
232
                if (func_num_args() > 1) {
1✔
233
                        trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED);
×
234
                        $default = func_get_arg(1);
×
235
                }
236
                return $this->params[$name] ?? $default ?? null;
1✔
237
        }
238

239

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

249

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

259

260
        /********************* interface SignalReceiver ****************d*g**/
261

262

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

275

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

284

285
        /********************* navigation ****************d*g**/
286

287

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

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

307

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

321

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

341
                return $this->getPresenter()->getLastCreatedRequestFlag('current');
1✔
342
        }
343

344

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

365
                $presenter->redirectUrl($url);
1✔
366
        }
367

368

369
        /**
370
         * Permanently redirects to presenter, action or signal.
371
         * @param  string   $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
372
         * @param  array|mixed  $args
373
         * @return never
374
         * @throws Nette\Application\AbortException
375
         */
376
        public function redirectPermanent(string $destination, $args = []): void
1✔
377
        {
378
                $args = func_num_args() < 3 && is_array($args)
1✔
379
                        ? $args
1✔
380
                        : array_slice(func_get_args(), 1);
1✔
381
                $presenter = $this->getPresenter();
1✔
382
                try {
383
                        $url = $presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect');
1✔
384
                } catch (InvalidRequestParameterException $e) {
×
385
                        $this->error($e->getMessage());
×
386
                }
387

388
                $presenter->redirectUrl($url, Nette\Http\IResponse::S301_MovedPermanently);
1✔
389
        }
390

391

392
        /**
393
         * Throws HTTP error.
394
         * @throws Nette\Application\BadRequestException
395
         * @return never
396
         */
397
        public function error(string $message = '', int $httpCode = Nette\Http\IResponse::S404_NotFound): void
1✔
398
        {
399
                throw new Nette\Application\BadRequestException($message, $httpCode);
1✔
400
        }
401
}
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