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

nette / component-model / 7159663623

10 Dec 2023 06:44PM UTC coverage: 84.793% (-1.3%) from 86.058%
7159663623

push

github

dg
monitor() without handlers triggers deprecation notice

0 of 1 new or added line in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

184 of 217 relevant lines covered (84.79%)

0.85 hits per line

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

83.65
/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

14

15
/**
16
 * Component is the base class for all components.
17
 *
18
 * Components are objects implementing IComponent. They has parent component and own name.
19
 *
20
 * @property-read string $name
21
 * @property-read IContainer|null $parent
22
 */
23
abstract class Component implements IComponent
24
{
25
        use Nette\SmartObject;
26

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

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

33

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

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

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

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

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

73
                return $this->monitors[$type][0];
1✔
74
        }
75

76

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

87

88
        /**
89
         * Starts monitoring of ancestors.
90
         */
91
        final public function monitor(string $type, ?callable $attached = null, ?callable $detached = null): void
1✔
92
        {
93
                if (func_num_args() === 1) {
1✔
NEW
94
                        trigger_error('The attached() and detached() methods are deprecated, use the second and third arguments of the monitor() method instead.', E_USER_DEPRECATED);
×
UNCOV
95
                        $attached = [$this, 'attached'];
×
UNCOV
96
                        $detached = [$this, 'detached'];
×
97
                }
98

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

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

110

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

119

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

129

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

139

140
        /********************* interface IComponent ****************d*g**/
141

142

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

148

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

157

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

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

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

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

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

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

195
                return $this;
1✔
196
        }
197

198

199
        /**
200
         * Is called by a component when it is about to be set new parent. Descendant can
201
         * override this method to disallow a parent change by throwing an Nette\InvalidStateException
202
         * @throws Nette\InvalidStateException
203
         */
204
        protected function validateParent(IContainer $parent): void
1✔
205
        {
206
        }
1✔
207

208

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

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

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

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

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

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

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

274

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

277

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

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

297

298
        /**
299
         * Prevents serialization.
300
         */
301
        final public function __sleep()
302
        {
303
                throw new Nette\NotImplementedException('Object serialization is not supported by class ' . static::class);
×
304
        }
305

306

307
        /**
308
         * Prevents unserialization.
309
         */
310
        final public function __wakeup()
311
        {
312
                throw new Nette\NotImplementedException('Object unserialization is not supported by class ' . static::class);
×
313
        }
314
}
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