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

nette / component-model / 7159764400

10 Dec 2023 07:07PM UTC coverage: 84.404%. Remained the same
7159764400

push

github

dg
used generics in phpDoc [WIP]

184 of 218 relevant lines covered (84.4%)

0.84 hits per line

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

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

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

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

35

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

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

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

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

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

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

79

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

91

92
        /**
93
         * Starts monitoring of ancestors.
94
         */
95
        final public function monitor(string $type, ?callable $attached = null, ?callable $detached = null): void
1✔
96
        {
97
                if (func_num_args() === 1) {
1✔
98
                        $class = (new \ReflectionMethod($this, 'attached'))->getDeclaringClass()->getName();
×
99
                        trigger_error(__METHOD__ . "(): Methods $class::attached() and $class::detached() are deprecated, use monitor(\$type, [attached], [detached])", E_USER_DEPRECATED);
×
100
                        $attached = [$this, 'attached'];
×
101
                        $detached = [$this, 'detached'];
×
102
                }
103

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

112
                $this->monitors[$type][3][] = [$attached, $detached]; // mark as monitored
1✔
113
        }
1✔
114

115

116
        /**
117
         * Stops monitoring of ancestors.
118
         */
119
        final public function unmonitor(string $type): void
120
        {
121
                unset($this->monitors[$type]);
×
122
        }
123

124

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

134

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

144

145
        /********************* interface IComponent ****************d*g**/
146

147

148
        final public function getName(): ?string
149
        {
150
                return $this->name;
1✔
151
        }
152

153

154
        /**
155
         * Returns the parent container if any.
156
         * @return T
157
         */
158
        final public function getParent(): ?IContainer
159
        {
160
                return $this->parent;
1✔
161
        }
162

163

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

177
                } elseif ($parent === $this->parent && $name === null) {
1✔
178
                        return $this; // nothing to do
×
179
                }
180

181
                // A component cannot be given a parent if it already has a parent.
182
                if ($this->parent !== null && $parent !== null) {
1✔
183
                        throw new Nette\InvalidStateException("Component '$this->name' already has a parent.");
1✔
184
                }
185

186
                // remove from parent?
187
                if ($parent === null) {
1✔
188
                        $this->refreshMonitors(0);
1✔
189
                        $this->parent = null;
1✔
190

191
                } else { // add to parent
192
                        $this->validateParent($parent);
1✔
193
                        $this->parent = $parent;
1✔
194
                        if ($name !== null) {
1✔
195
                                $this->name = $name;
1✔
196
                        }
197

198
                        $tmp = [];
1✔
199
                        $this->refreshMonitors(0, $tmp);
1✔
200
                }
201

202
                return $this;
1✔
203
        }
204

205

206
        /**
207
         * Is called by a component when it is about to be set new parent. Descendant can
208
         * override this method to disallow a parent change by throwing an Nette\InvalidStateException
209
         * @param  T  $parent
210
         * @throws Nette\InvalidStateException
211
         */
212
        protected function validateParent(IContainer $parent): void
1✔
213
        {
214
        }
1✔
215

216

217
        /**
218
         * Refreshes monitors.
219
         * @param  array<string,true>|null  $missing  (array = attaching, null = detaching)
220
         * @param  array<int,array{callable,IComponent}>  $listeners
221
         */
222
        private function refreshMonitors(int $depth, ?array &$missing = null, array &$listeners = []): void
1✔
223
        {
224
                if ($this instanceof IContainer) {
1✔
225
                        foreach ($this->getComponents() as $component) {
1✔
226
                                if ($component instanceof self) {
1✔
227
                                        $component->refreshMonitors($depth + 1, $missing, $listeners);
1✔
228
                                }
229
                        }
230
                }
231

232
                if ($missing === null) { // detaching
1✔
233
                        foreach ($this->monitors as $type => $rec) {
1✔
234
                                if (isset($rec[1]) && $rec[1] > $depth) {
1✔
235
                                        if ($rec[3]) { // monitored
1✔
236
                                                $this->monitors[$type] = [null, null, null, $rec[3]];
1✔
237
                                                foreach ($rec[3] as $pair) {
1✔
238
                                                        $listeners[] = [$pair[1], $rec[0]];
1✔
239
                                                }
240
                                        } else { // not monitored, just randomly cached
241
                                                unset($this->monitors[$type]);
1✔
242
                                        }
243
                                }
244
                        }
245
                } else { // attaching
246
                        foreach ($this->monitors as $type => $rec) {
1✔
247
                                if (isset($rec[0])) { // is in cache yet
1✔
248
                                        continue;
×
249

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

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

256
                                } else {
257
                                        unset($this->monitors[$type]); // forces re-lookup
1✔
258
                                        if ($obj = $this->lookup($type, throw: false)) {
1✔
259
                                                foreach ($rec[3] as $pair) {
1✔
260
                                                        $listeners[] = [$pair[0], $obj];
1✔
261
                                                }
262
                                        } else {
263
                                                $missing[$type] = true;
1✔
264
                                        }
265

266
                                        $this->monitors[$type][3] = $rec[3]; // mark as monitored
1✔
267
                                }
268
                        }
269
                }
270

271
                if ($depth === 0) { // call listeners
1✔
272
                        $prev = [];
1✔
273
                        foreach ($listeners as $item) {
1✔
274
                                if ($item[0] && !in_array($item, $prev, strict: true)) {
1✔
275
                                        $item[0]($item[1]);
1✔
276
                                        $prev[] = $item;
1✔
277
                                }
278
                        }
279
                }
280
        }
1✔
281

282

283
        /********************* cloneable, serializable ****************d*g**/
284

285

286
        /**
287
         * Object cloning.
288
         */
289
        public function __clone()
290
        {
291
                if ($this->parent === null) {
1✔
292
                        return;
×
293

294
                } elseif ($this->parent instanceof Container) {
1✔
295
                        $this->parent = $this->parent->_isCloning();
1✔
296
                        if ($this->parent === null) { // not cloning
1✔
297
                                $this->refreshMonitors(0);
1✔
298
                        }
299
                } else {
300
                        $this->parent = null;
×
301
                        $this->refreshMonitors(0);
×
302
                }
303
        }
1✔
304

305

306
        /**
307
         * Prevents serialization.
308
         */
309
        final public function __sleep()
310
        {
311
                throw new Nette\NotImplementedException('Object serialization is not supported by class ' . static::class);
×
312
        }
313

314

315
        /**
316
         * Prevents unserialization.
317
         */
318
        final public function __wakeup()
319
        {
320
                throw new Nette\NotImplementedException('Object unserialization is not supported by class ' . static::class);
×
321
        }
322
}
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