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

nette / component-model / 15762957805

19 Jun 2025 05:00PM UTC coverage: 85.514%. Remained the same
15762957805

push

github

dg
removed attached() & detached() methods (BC break)

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

25 existing lines in 4 files now uncovered.

183 of 214 relevant lines covered (85.51%)

0.86 hits per line

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

85.29
/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
 * @template T of IContainer
20
 * @implements IComponent<T>
21
 * @property-read string $name
22
 * @property-read T|null $parent
23
 */
24
abstract class Component implements IComponent
25
{
26
        use Nette\SmartObject;
27

28
        private ?IContainer $parent = null;
29
        private ?string $name = null;
30

31
        /** @var array<string, array{?IComponent, ?int, ?string, array<int, array{?callable, ?callable}>}> means [type => [obj, depth, path, [attached, detached]]] */
32
        private array $monitors = [];
33

34

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

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

60
                        if ($obj) {
1✔
61
                                $this->monitors[$type] = [$obj, $depth, substr($path, 1), []];
1✔
62

63
                        } else {
64
                                $this->monitors[$type] = [null, null, null, []]; // not found
1✔
65
                        }
66
                }
67

68
                if ($throw && $this->monitors[$type][0] === null) {
1✔
69
                        $message = $this->name !== null
1✔
70
                                ? "Component '$this->name' is not attached to '$type'."
1✔
UNCOV
71
                                : "Component of type '" . static::class . "' is not attached to '$type'.";
×
72
                        throw new Nette\InvalidStateException($message);
1✔
73
                }
74

75
                return $this->monitors[$type][0];
1✔
76
        }
77

78

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

90

91
        /**
92
         * Starts monitoring ancestors for attach/detach events.
93
         */
94
        final public function monitor(string $type, ?callable $attached = null, ?callable $detached = null): void
1✔
95
        {
96
                if (!$attached && !$detached) {
1✔
NEW
97
                        throw new Nette\InvalidStateException('At least one handler is required.');
×
98
                }
99

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

108
                $this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored
1✔
109
        }
1✔
110

111

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

120

121
        /********************* interface IComponent ****************d*g**/
122

123

124
        final public function getName(): ?string
125
        {
126
                return $this->name;
1✔
127
        }
128

129

130
        /**
131
         * Returns the parent container if any.
132
         * @return T
133
         */
134
        final public function getParent(): ?IContainer
135
        {
136
                return $this->parent;
1✔
137
        }
138

139

140
        /**
141
         * Sets or removes the parent of this component. This method is managed by containers and should
142
         * not be called by applications
143
         * @param  T  $parent
144
         * @throws Nette\InvalidStateException
145
         * @internal
146
         */
147
        public function setParent(?IContainer $parent, ?string $name = null): static
1✔
148
        {
149
                if ($parent === null && $this->parent === null && $name !== null) {
1✔
UNCOV
150
                        $this->name = $name; // just rename
×
UNCOV
151
                        return $this;
×
152

153
                } elseif ($parent === $this->parent && $name === null) {
1✔
UNCOV
154
                        return $this; // nothing to do
×
155
                }
156

157
                // A component cannot be given a parent if it already has a parent.
158
                if ($this->parent !== null && $parent !== null) {
1✔
159
                        throw new Nette\InvalidStateException("Component '$this->name' already has a parent.");
1✔
160
                }
161

162
                // remove from parent?
163
                if ($parent === null) {
1✔
164
                        $this->refreshMonitors(0);
1✔
165
                        $this->parent = null;
1✔
166

167
                } else { // add to parent
168
                        $this->validateParent($parent);
1✔
169
                        $this->parent = $parent;
1✔
170
                        if ($name !== null) {
1✔
171
                                $this->name = $name;
1✔
172
                        }
173

174
                        $tmp = [];
1✔
175
                        $this->refreshMonitors(0, $tmp);
1✔
176
                }
177

178
                return $this;
1✔
179
        }
180

181

182
        /**
183
         * Validates the new parent before it's set.
184
         * Descendant classes can override this to implement custom validation logic.
185
         * @param  T  $parent
186
         * @throws Nette\InvalidStateException
187
         */
188
        protected function validateParent(IContainer $parent): void
1✔
189
        {
190
        }
1✔
191

192

193
        /**
194
         * Refreshes monitors.
195
         * @param  array<string,true>|null  $missing  (array = attaching, null = detaching)
196
         * @param  array<int,array{callable,IComponent}>  $listeners
197
         */
198
        private function refreshMonitors(int $depth, ?array &$missing = null, array &$listeners = []): void
1✔
199
        {
200
                if ($this instanceof IContainer) {
1✔
201
                        foreach ($this->getComponents() as $component) {
1✔
202
                                if ($component instanceof self) {
1✔
203
                                        $component->refreshMonitors($depth + 1, $missing, $listeners);
1✔
204
                                }
205
                        }
206
                }
207

208
                if ($missing === null) { // detaching
1✔
209
                        foreach ($this->monitors as $type => $rec) {
1✔
210
                                if (isset($rec[1]) && $rec[1] > $depth) {
1✔
211
                                        if ($rec[3]) { // monitored
1✔
212
                                                $this->monitors[$type] = [null, null, null, $rec[3]];
1✔
213
                                                foreach ($rec[3] as $pair) {
1✔
214
                                                        $listeners[] = [$pair[1], $rec[0]];
1✔
215
                                                }
216
                                        } else { // not monitored, just randomly cached
217
                                                unset($this->monitors[$type]);
1✔
218
                                        }
219
                                }
220
                        }
221
                } else { // attaching
222
                        foreach ($this->monitors as $type => $rec) {
1✔
223
                                if (isset($rec[0])) { // is in cache yet
1✔
UNCOV
224
                                        continue;
×
225

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

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

232
                                } else {
233
                                        unset($this->monitors[$type]); // forces re-lookup
1✔
234
                                        if ($obj = $this->lookup($type, throw: false)) {
1✔
235
                                                foreach ($rec[3] as $pair) {
1✔
236
                                                        $listeners[] = [$pair[0], $obj];
1✔
237
                                                }
238
                                        } else {
239
                                                $missing[$type] = true;
1✔
240
                                        }
241

242
                                        $this->monitors[$type][3] = $rec[3]; // mark as monitored
1✔
243
                                }
244
                        }
245
                }
246

247
                if ($depth === 0) { // call listeners
1✔
248
                        $prev = [];
1✔
249
                        foreach ($listeners as $item) {
1✔
250
                                if ($item[0] && !in_array($item, $prev, strict: true)) {
1✔
251
                                        $item[0]($item[1]);
1✔
252
                                        $prev[] = $item;
1✔
253
                                }
254
                        }
255
                }
256
        }
1✔
257

258

259
        /********************* cloneable, serializable ****************d*g**/
260

261

262
        /**
263
         * Object cloning.
264
         */
265
        public function __clone()
266
        {
267
                if ($this->parent === null) {
1✔
UNCOV
268
                        return;
×
269

270
                } elseif ($this->parent instanceof Container) {
1✔
271
                        $this->parent = $this->parent->_isCloning();
1✔
272
                        if ($this->parent === null) { // not cloning
1✔
273
                                $this->refreshMonitors(0);
1✔
274
                        }
275
                } else {
UNCOV
276
                        $this->parent = null;
×
UNCOV
277
                        $this->refreshMonitors(0);
×
278
                }
279
        }
1✔
280

281

282
        /**
283
         * Prevents serialization.
284
         */
285
        final public function __sleep()
286
        {
UNCOV
287
                throw new Nette\NotImplementedException('Object serialization is not supported by class ' . static::class);
×
288
        }
289

290

291
        /**
292
         * Prevents unserialization.
293
         */
294
        final public function __wakeup()
295
        {
UNCOV
296
                throw new Nette\NotImplementedException('Object unserialization is not supported by class ' . static::class);
×
297
        }
298
}
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