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

nette / component-model / 21197769630

21 Jan 2026 04:55AM UTC coverage: 87.387% (-0.06%) from 87.448%
21197769630

push

github

dg
getComponents() parameters deprecated

2 of 2 new or added lines in 1 file covered. (100.0%)

10 existing lines in 1 file now uncovered.

194 of 222 relevant lines covered (87.39%)

0.87 hits per line

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

88.99
/src/ComponentModel/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\ComponentModel;
11

12
use Nette;
13
use function func_num_args, in_array, substr;
14

15

16
/**
17
 * Base class for all components. Components have a parent, name, and can be monitored by ancestors.
18
 *
19
 * @property-deprecated string $name
20
 * @property-deprecated ?IContainer $parent
21
 */
22
abstract class Component implements IComponent
23
{
24
        use Nette\SmartObject;
25

26
        private ?IContainer $parent = null;
27
        private ?string $name = null;
28

29
        /**
30
         * Monitors: tracks monitored ancestors and registered callbacks.
31
         * Combines cached lookup results with callback registrations for each monitored type.
32
         * Depth is used to detect when monitored ancestor becomes unreachable during detachment.
33
         * Structure: [type => [found object, depth to object, path to object, [[attached, detached], ...]]]
34
         * @var array<''|class-string<Nette\ComponentModel\IComponent>, array{?IComponent, ?int, ?string, array<int, array{?\Closure, ?\Closure}>}>
35
         */
36
        private array $monitors = [];
37

38
        /** Prevents nested listener execution during refreshMonitors */
39
        private bool $callingListeners = false;
40

41

42
        /**
43
         * Finds the closest ancestor of specified type.
44
         * @template T of IComponent
45
         * @param ?class-string<T>  $type
46
         * @param bool  $throw  throw exception if component doesn't exist?
47
         * @return ($type is null ? ($throw is true ? IComponent : ?IComponent) : ($throw is true ? T : ?T))
48
         */
49
        final public function lookup(?string $type, bool $throw = true): ?IComponent
1✔
50
        {
51
                $type ??= '';
1✔
52
                if (!isset($this->monitors[$type])) { // not monitored or not processed yet
1✔
53
                        $ancestor = $this->parent;
1✔
54
                        $path = self::NameSeparator . $this->name;
1✔
55
                        $depth = 1;
1✔
56
                        while ($ancestor !== null) {
1✔
57
                                $parent = $ancestor->getParent();
1✔
58
                                if ($type ? $ancestor instanceof $type : $parent === null) {
1✔
59
                                        break;
1✔
60
                                }
61

62
                                $path = self::NameSeparator . $ancestor->getName() . $path;
1✔
63
                                $depth++;
1✔
64
                                $ancestor = $parent; // IComponent::getParent()
1✔
65
                                if ($ancestor === $this) {
1✔
UNCOV
66
                                        $ancestor = null; // prevent cycling
×
67
                                }
68
                        }
69

70
                        $this->monitors[$type] = $ancestor
1✔
71
                                ? [$ancestor, $depth, substr($path, 1), []]
1✔
72
                                : [null, null, null, []]; // not found
1✔
73
                }
74

75
                if ($throw && $this->monitors[$type][0] === null) {
1✔
76
                        $desc = $this->name === null ? "type of '" . static::class . "'" : "'$this->name'";
1✔
77
                        throw new Nette\InvalidStateException("Component $desc is not attached to '$type'.");
1✔
78
                }
79

80
                return $this->monitors[$type][0];
1✔
81
        }
82

83

84
        /**
85
         * Finds the closest ancestor specified by class or interface name and returns backtrace path.
86
         * A path is the concatenation of component names separated by self::NameSeparator.
87
         * @param ?class-string<IComponent>  $type
88
         * @param bool  $throw  throw exception if component doesn't exist?
89
         * @return ($throw is true ? string : ?string)
90
         */
91
        final public function lookupPath(?string $type = null, bool $throw = true): ?string
1✔
92
        {
93
                $this->lookup($type, $throw);
1✔
94
                return $this->monitors[$type ?? ''][2];
1✔
95
        }
96

97

98
        /**
99
         * Starts monitoring ancestors for attach/detach events.
100
         * @template T of IComponent
101
         * @param class-string<T>  $type
102
         * @param ?(callable(T): void)  $attached  called when attached to a monitored ancestor
103
         * @param ?(callable(T): void)  $detached  called before detaching from a monitored ancestor
104
         */
105
        final public function monitor(string $type, ?callable $attached = null, ?callable $detached = null): void
1✔
106
        {
107
                if (func_num_args() === 1) {
1✔
108
                        $class = (new \ReflectionMethod($this, 'attached'))->getDeclaringClass()->getName();
1✔
109
                        trigger_error(__METHOD__ . "(): Methods $class::attached() and $class::detached() are deprecated, use monitor(\$type, [attached], [detached])", E_USER_DEPRECATED);
1✔
110
                        $attached = $this->attached(...);
1✔
111
                        $detached = $this->detached(...);
1✔
112
                } else {
113
                        $attached = $attached ? $attached(...) : null;
1✔
114
                        $detached = $detached ? $detached(...) : null;
1✔
115
                }
116

117
                if (
118
                        $attached
1✔
119
                        && ($ancestor = $this->lookup($type, throw: false))
1✔
120
                        && !in_array([$attached, $detached], $this->monitors[$type][3], strict: false)
1✔
121
                ) {
122
                        $attached($ancestor);
1✔
123
                }
124

125
                $this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored
1✔
126
        }
1✔
127

128

129
        /**
130
         * Stops monitoring ancestors of specified type.
131
         * @param class-string<IComponent>  $type
132
         */
133
        final public function unmonitor(string $type): void
134
        {
UNCOV
135
                unset($this->monitors[$type]);
×
136
        }
137

138

139
        /**
140
         * This method will be called when the component (or component's parent)
141
         * becomes attached to a monitored object. Do not call this method yourself.
142
         * @deprecated  use monitor($type, $attached)
143
         */
144
        protected function attached(IComponent $obj): void
145
        {
146
        }
147

148

149
        /**
150
         * This method will be called before the component (or component's parent)
151
         * becomes detached from a monitored object. Do not call this method yourself.
152
         * @deprecated  use monitor($type, null, $detached)
153
         */
154
        protected function detached(IComponent $obj): void
155
        {
156
        }
157

158

159
        /********************* interface IComponent ****************d*g**/
160

161

162
        final public function getName(): ?string
163
        {
164
                return $this->name;
1✔
165
        }
166

167

168
        /**
169
         * Returns the parent container if any.
170
         */
171
        final public function getParent(): ?IContainer
172
        {
173
                return $this->parent;
1✔
174
        }
175

176

177
        /**
178
         * Sets or removes the parent of this component. This method is managed by containers and should
179
         * not be called by applications
180
         * @throws Nette\InvalidStateException
181
         * @internal
182
         */
183
        public function setParent(?IContainer $parent, ?string $name = null): static
1✔
184
        {
185
                if ($parent === null && $this->parent === null && $name !== null) {
1✔
UNCOV
186
                        $this->name = $name; // just rename
×
187
                        return $this;
×
188

189
                } elseif ($parent === $this->parent && $name === null) {
1✔
UNCOV
190
                        return $this; // nothing to do
×
191
                }
192

193
                // A component cannot be given a parent if it already has a parent.
194
                if ($this->parent !== null && $parent !== null) {
1✔
195
                        throw new Nette\InvalidStateException("Component '$this->name' already has a parent.");
1✔
196
                }
197

198
                // remove from parent
199
                if ($parent === null) {
1✔
200
                        $this->refreshMonitors(0);
1✔
201
                        $this->parent = null;
1✔
202

203
                } else { // add to parent
204
                        $this->validateParent($parent);
1✔
205
                        $this->parent = $parent;
1✔
206
                        if ($name !== null) {
1✔
207
                                $this->name = $name;
1✔
208
                        }
209

210
                        $tmp = [];
1✔
211
                        $this->refreshMonitors(0, $tmp);
1✔
212
                }
213

214
                return $this;
1✔
215
        }
216

217

218
        /**
219
         * Validates the new parent before it's set.
220
         * Descendant classes can override this to implement custom validation logic.
221
         * @throws Nette\InvalidStateException
222
         */
223
        protected function validateParent(IContainer $parent): void
1✔
224
        {
225
        }
1✔
226

227

228
        /**
229
         * Refreshes monitors.
230
         * @param  ?array<string, true>  $missing  (array = attaching, null = detaching)
231
         * @param  array<int, array{?\Closure, IComponent}>  $listeners
232
         */
233
        private function refreshMonitors(int $depth, ?array &$missing = null, array &$listeners = []): void
1✔
234
        {
235
                if ($this instanceof IContainer) {
1✔
236
                        foreach ($this->getComponents() as $component) {
1✔
237
                                if ($component instanceof self) {
1✔
238
                                        $component->refreshMonitors($depth + 1, $missing, $listeners);
1✔
239
                                }
240
                        }
241
                }
242

243
                if ($missing === null) { // detaching
1✔
244
                        foreach ($this->monitors as $type => [$ancestor, $inDepth, , $callbacks]) {
1✔
245
                                if (isset($inDepth) && $inDepth > $depth) {
1✔
246
                                        assert($ancestor !== null);
247
                                        if ($callbacks) { // monitored
1✔
248
                                                $this->monitors[$type] = [null, null, null, $callbacks];
1✔
249
                                                foreach ($callbacks as [, $detached]) {
1✔
250
                                                        $listeners[] = [$detached, $ancestor];
1✔
251
                                                }
252
                                        } else { // not monitored, just randomly cached
UNCOV
253
                                                unset($this->monitors[$type]);
×
254
                                        }
255
                                }
256
                        }
257
                } else { // attaching
258
                        foreach ($this->monitors as $type => [$ancestor, , , $callbacks]) {
1✔
259
                                if (isset($ancestor)) { // is in cache yet
1✔
260
                                        continue;
1✔
261

262
                                } elseif (!$callbacks) { // not monitored, just randomly cached
1✔
UNCOV
263
                                        unset($this->monitors[$type]);
×
264

265
                                } elseif (isset($missing[$type])) { // known from previous lookup
1✔
UNCOV
266
                                        $this->monitors[$type] = [null, null, null, $callbacks];
×
267

268
                                } else {
269
                                        unset($this->monitors[$type]); // forces re-lookup
1✔
270
                                        assert($type !== '');
271
                                        if ($ancestor = $this->lookup($type, throw: false)) {
1✔
272
                                                foreach ($callbacks as [$attached]) {
1✔
273
                                                        $listeners[] = [$attached, $ancestor];
1✔
274
                                                }
275
                                        } else {
276
                                                $missing[$type] = true;
1✔
277
                                        }
278

279
                                        $this->monitors[$type][3] = $callbacks; // mark as monitored
1✔
280
                                }
281
                        }
282
                }
283

284
                if ($depth === 0 && !$this->callingListeners) { // call listeners
1✔
285
                        $this->callingListeners = true;
1✔
286
                        try {
287
                                $called = [];
1✔
288
                                foreach ($listeners as [$callback, $component]) {
1✔
289
                                        $key = [$callback, spl_object_id($component)];
1✔
290
                                        if ($callback && !in_array($key, $called, strict: false)) {
1✔
291
                                                $callback($component);
1✔
292
                                                $called[] = $key;
1✔
293
                                        }
294
                                }
295
                        } finally {
1✔
296
                                $this->callingListeners = false;
1✔
297
                        }
298
                }
299
        }
1✔
300

301

302
        /********************* cloneable, serializable ****************d*g**/
303

304

305
        /**
306
         * Object cloning.
307
         */
308
        public function __clone()
309
        {
310
                if ($this->parent === null) {
1✔
UNCOV
311
                        return;
×
312

313
                } elseif ($this->parent instanceof Container) {
1✔
314
                        $this->parent = $this->parent->_isCloning();
1✔
315
                        if ($this->parent === null) { // not cloning
1✔
316
                                $this->refreshMonitors(0);
1✔
317
                        }
318
                } else {
319
                        $this->parent = null;
×
UNCOV
320
                        $this->refreshMonitors(0);
×
321
                }
322
        }
1✔
323

324

325
        /**
326
         * Prevents serialization.
327
         */
328
        final public function __serialize()
329
        {
UNCOV
330
                throw new Nette\NotImplementedException('Object serialization is not supported by class ' . static::class);
×
331
        }
332
}
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