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

nette / component-model / 20531414929

26 Dec 2025 11:50PM UTC coverage: 85.116%. Remained the same
20531414929

push

github

dg
refactoring

7 of 10 new or added lines in 2 files covered. (70.0%)

21 existing lines in 2 files now uncovered.

183 of 215 relevant lines covered (85.12%)

0.85 hits per line

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

84.62
/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-read string $name
20
 * @property-deprecated IContainer|null $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
        /** @var array<string, array{?IComponent, ?int, ?string, array<int, array{?callable, ?callable}>}> means [type => [obj, depth, path, [attached, detached]]] */
30
        private array $monitors = [];
31

32

33
        /**
34
         * Finds the closest ancestor of specified type.
35
         * @param  bool  $throw   throw exception if component doesn't exist?
36
         * @return ($throw is true ? IComponent : ?IComponent)
37
         */
38
        final public function lookup(?string $type, bool $throw = true): ?IComponent
1✔
39
        {
40
                $type ??= '';
1✔
41
                if (!isset($this->monitors[$type])) { // not monitored or not processed yet
1✔
42
                        $obj = $this->parent;
1✔
43
                        $path = self::NameSeparator . $this->name;
1✔
44
                        $depth = 1;
1✔
45
                        while ($obj !== null) {
1✔
46
                                $parent = $obj->getParent();
1✔
47
                                if ($type ? $obj instanceof $type : $parent === null) {
1✔
48
                                        break;
1✔
49
                                }
50

51
                                $path = self::NameSeparator . $obj->getName() . $path;
1✔
52
                                $depth++;
1✔
53
                                $obj = $parent; // IComponent::getParent()
1✔
54
                                if ($obj === $this) {
1✔
55
                                        $obj = null; // prevent cycling
×
56
                                }
57
                        }
58

59
                        $this->monitors[$type] = $obj
1✔
60
                                ? [$obj, $depth, substr($path, 1), []]
1✔
61
                                : [null, null, null, []]; // not found
1✔
62
                }
63

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

69
                return $this->monitors[$type][0];
1✔
70
        }
71

72

73
        /**
74
         * Finds the closest ancestor specified by class or interface name and returns backtrace path.
75
         * A path is the concatenation of component names separated by self::NameSeparator.
76
         * @return ($throw is true ? string : ?string)
77
         */
78
        final public function lookupPath(?string $type = null, bool $throw = true): ?string
1✔
79
        {
80
                $this->lookup($type, $throw);
1✔
81
                return $this->monitors[$type ?? ''][2];
1✔
82
        }
83

84

85
        /**
86
         * Starts monitoring ancestors for attach/detach events.
87
         */
88
        final public function monitor(string $type, ?callable $attached = null, ?callable $detached = null): void
1✔
89
        {
90
                if (func_num_args() === 1) {
1✔
UNCOV
91
                        $class = (new \ReflectionMethod($this, 'attached'))->getDeclaringClass()->getName();
×
UNCOV
92
                        trigger_error(__METHOD__ . "(): Methods $class::attached() and $class::detached() are deprecated, use monitor(\$type, [attached], [detached])", E_USER_DEPRECATED);
×
UNCOV
93
                        $attached = $this->attached(...);
×
UNCOV
94
                        $detached = $this->detached(...);
×
95
                }
96

97
                if (
98
                        ($obj = $this->lookup($type, throw: false))
1✔
99
                        && $attached
1✔
100
                        && !in_array([$attached, $detached], $this->monitors[$type][3], strict: true)
1✔
101
                ) {
102
                        $attached($obj);
1✔
103
                }
104

105
                $this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored
1✔
106
        }
1✔
107

108

109
        /**
110
         * Stops monitoring ancestors of specified type.
111
         */
112
        final public function unmonitor(string $type): void
113
        {
UNCOV
114
                unset($this->monitors[$type]);
×
115
        }
116

117

118
        /**
119
         * This method will be called when the component (or component's parent)
120
         * becomes attached to a monitored object. Do not call this method yourself.
121
         * @deprecated  use monitor($type, $attached)
122
         */
123
        protected function attached(IComponent $obj): void
124
        {
125
        }
126

127

128
        /**
129
         * This method will be called before the component (or component's parent)
130
         * becomes detached from a monitored object. Do not call this method yourself.
131
         * @deprecated  use monitor($type, null, $detached)
132
         */
133
        protected function detached(IComponent $obj): void
134
        {
135
        }
136

137

138
        /********************* interface IComponent ****************d*g**/
139

140

141
        final public function getName(): ?string
142
        {
143
                return $this->name;
1✔
144
        }
145

146

147
        /**
148
         * Returns the parent container if any.
149
         */
150
        final public function getParent(): ?IContainer
151
        {
152
                return $this->parent;
1✔
153
        }
154

155

156
        /**
157
         * Sets or removes the parent of this component. This method is managed by containers and should
158
         * not be called by applications
159
         * @throws Nette\InvalidStateException
160
         * @internal
161
         */
162
        public function setParent(?IContainer $parent, ?string $name = null): static
1✔
163
        {
164
                if ($parent === null && $this->parent === null && $name !== null) {
1✔
UNCOV
165
                        $this->name = $name; // just rename
×
UNCOV
166
                        return $this;
×
167

168
                } elseif ($parent === $this->parent && $name === null) {
1✔
UNCOV
169
                        return $this; // nothing to do
×
170
                }
171

172
                // A component cannot be given a parent if it already has a parent.
173
                if ($this->parent !== null && $parent !== null) {
1✔
174
                        throw new Nette\InvalidStateException("Component '$this->name' already has a parent.");
1✔
175
                }
176

177
                // remove from parent
178
                if ($parent === null) {
1✔
179
                        $this->refreshMonitors(0);
1✔
180
                        $this->parent = null;
1✔
181

182
                } else { // add to parent
183
                        $this->validateParent($parent);
1✔
184
                        $this->parent = $parent;
1✔
185
                        if ($name !== null) {
1✔
186
                                $this->name = $name;
1✔
187
                        }
188

189
                        $tmp = [];
1✔
190
                        $this->refreshMonitors(0, $tmp);
1✔
191
                }
192

193
                return $this;
1✔
194
        }
195

196

197
        /**
198
         * Validates the new parent before it's set.
199
         * Descendant classes can override this to implement custom validation logic.
200
         * @throws Nette\InvalidStateException
201
         */
202
        protected function validateParent(IContainer $parent): void
1✔
203
        {
204
        }
1✔
205

206

207
        /**
208
         * Refreshes monitors.
209
         * @param  array<string,true>|null  $missing  (array = attaching, null = detaching)
210
         * @param  array<int,array{callable,IComponent}>  $listeners
211
         */
212
        private function refreshMonitors(int $depth, ?array &$missing = null, array &$listeners = []): void
1✔
213
        {
214
                if ($this instanceof IContainer) {
1✔
215
                        foreach ($this->getComponents() as $component) {
1✔
216
                                if ($component instanceof self) {
1✔
217
                                        $component->refreshMonitors($depth + 1, $missing, $listeners);
1✔
218
                                }
219
                        }
220
                }
221

222
                if ($missing === null) { // detaching
1✔
223
                        foreach ($this->monitors as $type => $rec) {
1✔
224
                                if (isset($rec[1]) && $rec[1] > $depth) {
1✔
225
                                        if ($rec[3]) { // monitored
1✔
226
                                                $this->monitors[$type] = [null, null, null, $rec[3]];
1✔
227
                                                foreach ($rec[3] as $pair) {
1✔
228
                                                        $listeners[] = [$pair[1], $rec[0]];
1✔
229
                                                }
230
                                        } else { // not monitored, just randomly cached
231
                                                unset($this->monitors[$type]);
1✔
232
                                        }
233
                                }
234
                        }
235
                } else { // attaching
236
                        $ofs = 0;
1✔
237
                        foreach ($this->monitors as $type => $rec) {
1✔
238
                                if (isset($rec[0])) { // is in cache yet
1✔
UNCOV
239
                                        continue;
×
240

241
                                } elseif (!$rec[3]) { // not monitored, just randomly cached
1✔
UNCOV
242
                                        unset($this->monitors[$type]);
×
243

244
                                } elseif (isset($missing[$type])) { // known from previous lookup
1✔
UNCOV
245
                                        $this->monitors[$type] = [null, null, null, $rec[3]];
×
246

247
                                } else {
248
                                        unset($this->monitors[$type]); // forces re-lookup
1✔
249
                                        if ($obj = $this->lookup($type, throw: false)) {
1✔
250
                                                foreach ($rec[3] as $pair) {
1✔
251
                                                        array_splice($listeners, $ofs++, 0, [[$pair[0], $obj]]);
1✔
252
                                                }
253
                                        } else {
254
                                                $missing[$type] = true;
1✔
255
                                        }
256

257
                                        $this->monitors[$type][3] = $rec[3]; // mark as monitored
1✔
258
                                }
259
                        }
260
                }
261

262
                if ($depth === 0) { // call listeners
1✔
263
                        $prev = [];
1✔
264
                        foreach ($listeners as $item) {
1✔
265
                                if ($item[0] && !in_array($item, $prev, strict: true)) {
1✔
266
                                        $item[0]($item[1]);
1✔
267
                                        $prev[] = $item;
1✔
268
                                }
269
                        }
270
                }
271
        }
1✔
272

273

274
        /********************* cloneable, serializable ****************d*g**/
275

276

277
        /**
278
         * Object cloning.
279
         */
280
        public function __clone()
281
        {
282
                if ($this->parent === null) {
1✔
UNCOV
283
                        return;
×
284

285
                } elseif ($this->parent instanceof Container) {
1✔
286
                        $this->parent = $this->parent->_isCloning();
1✔
287
                        if ($this->parent === null) { // not cloning
1✔
288
                                $this->refreshMonitors(0);
1✔
289
                        }
290
                } else {
UNCOV
291
                        $this->parent = null;
×
UNCOV
292
                        $this->refreshMonitors(0);
×
293
                }
294
        }
1✔
295

296

297
        /**
298
         * Prevents serialization.
299
         */
300
        final public function __serialize()
301
        {
UNCOV
302
                throw new Nette\NotImplementedException('Object serialization is not supported by class ' . static::class);
×
303
        }
304
}
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

© 2025 Coveralls, Inc