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

nette / component-model / 20520823674

26 Dec 2025 10:24AM UTC coverage: 85.116% (+0.3%) from 84.793%
20520823674

push

github

dg
refactoring

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

6 existing lines in 1 file 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

86.6
/src/ComponentModel/Container.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 array_filter, array_keys, array_map, array_merge, assert, explode, func_get_args, get_class_methods, method_exists, preg_filter, preg_match, reset, ucfirst;
14

15

16
/**
17
 * Manages a collection of child components.
18
 *
19
 * @property-deprecated IComponent[] $components
20
 */
21
class Container extends Component implements IContainer
22
{
23
        private const NameRegexp = '#^[a-zA-Z0-9_]+$#D';
24

25
        /** @var IComponent[] */
26
        private array $components = [];
27
        private ?Container $cloning = null;
28

29

30
        /********************* interface IContainer ****************d*g**/
31

32

33
        /**
34
         * Adds a child component to the container.
35
         * @return static
36
         * @throws Nette\InvalidStateException
37
         */
38
        public function addComponent(IComponent $component, ?string $name, ?string $insertBefore = null)
1✔
39
        {
40
                if ($name === null) {
1✔
41
                        $name = $component->getName();
×
42
                        if ($name === null) {
×
43
                                throw new Nette\InvalidStateException("Missing component's name.");
×
44
                        }
45
                }
46

47
                if (!preg_match(self::NameRegexp, $name)) {
1✔
48
                        throw new Nette\InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
×
49

50
                } elseif (isset($this->components[$name])) {
1✔
51
                        throw new Nette\InvalidStateException("Component with name '$name' already exists.");
×
52
                }
53

54
                // check circular reference
55
                $obj = $this;
1✔
56
                do {
57
                        if ($obj === $component) {
1✔
58
                                throw new Nette\InvalidStateException("Circular reference detected while adding component '$name'.");
×
59
                        }
60
                } while (($obj = $obj->getParent()) !== null);
1✔
61

62
                // user checking
63
                $this->validateChildComponent($component);
1✔
64

65
                if ($insertBefore !== null && isset($this->components[$insertBefore])) {
1✔
66
                        $tmp = [];
1✔
67
                        foreach ($this->components as $k => $v) {
1✔
68
                                if ((string) $k === $insertBefore) {
1✔
69
                                        $tmp[$name] = $component;
1✔
70
                                }
71

72
                                $tmp[$k] = $v;
1✔
73
                        }
74

75
                        $this->components = $tmp;
1✔
76
                } else {
77
                        $this->components[$name] = $component;
1✔
78
                }
79

80
                try {
81
                        $component->setParent($this, $name);
1✔
82
                } catch (\Throwable $e) {
1✔
83
                        unset($this->components[$name]); // undo
1✔
84
                        throw $e;
1✔
85
                }
86

87
                return $this;
1✔
88
        }
89

90

91
        /**
92
         * Removes a child component from the container.
93
         */
94
        public function removeComponent(IComponent $component): void
1✔
95
        {
96
                $name = $component->getName();
1✔
97
                if (($this->components[$name] ?? null) !== $component) {
1✔
98
                        throw new Nette\InvalidArgumentException("Component named '$name' is not located in this container.");
×
99
                }
100

101
                unset($this->components[$name]);
1✔
102
                $component->setParent(null);
1✔
103
        }
1✔
104

105

106
        /**
107
         * Retrieves a child component by name or creates it if it doesn't exist.
108
         * @param  bool  $throw  throw exception if component doesn't exist?
109
         * @return ($throw is true ? IComponent : ?IComponent)
110
         */
111
        final public function getComponent(string $name, bool $throw = true): ?IComponent
1✔
112
        {
113
                [$name] = $parts = explode(self::NameSeparator, $name, 2);
1✔
114

115
                if (!isset($this->components[$name])) {
1✔
116
                        if (!preg_match(self::NameRegexp, $name)) {
1✔
NEW
117
                                return $throw
×
NEW
118
                                        ? throw new Nette\InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.")
×
NEW
119
                                        : null;
×
120
                        }
121

122
                        $component = $this->createComponent($name);
1✔
123
                        if ($component && !isset($this->components[$name])) {
1✔
124
                                $this->addComponent($component, $name);
1✔
125
                        }
126
                }
127

128
                $component = $this->components[$name] ?? null;
1✔
129
                if ($component !== null) {
1✔
130
                        if (!isset($parts[1])) {
1✔
131
                                return $component;
1✔
132

133
                        } elseif ($component instanceof IContainer) {
1✔
134
                                return $component->getComponent($parts[1], $throw);
1✔
135

136
                        } elseif ($throw) {
×
137
                                throw new Nette\InvalidArgumentException("Component with name '$name' is not container and cannot have '$parts[1]' component.");
×
138
                        }
139
                } elseif ($throw) {
1✔
140
                        $hint = Nette\Utils\ObjectHelpers::getSuggestion(array_merge(
1✔
141
                                array_map('strval', array_keys($this->components)),
1✔
142
                                array_map('lcfirst', preg_filter('#^createComponent([A-Z0-9].*)#', '$1', get_class_methods($this))),
1✔
143
                        ), $name);
144
                        throw new Nette\InvalidArgumentException("Component with name '$name' does not exist" . ($hint ? ", did you mean '$hint'?" : '.'));
1✔
145
                }
146

147
                return null;
×
148
        }
149

150

151
        /**
152
         * Creates a new component. Delegates creation to createComponent<Name> method if it exists.
153
         */
154
        protected function createComponent(string $name): ?IComponent
1✔
155
        {
156
                $ucname = ucfirst($name);
1✔
157
                $method = 'createComponent' . $ucname;
1✔
158
                if (
159
                        $ucname !== $name
1✔
160
                        && method_exists($this, $method)
1✔
161
                        && (new \ReflectionMethod($this, $method))->getName() === $method
1✔
162
                ) {
163
                        $component = $this->$method($name);
1✔
164
                        if (!$component instanceof IComponent && !isset($this->components[$name])) {
1✔
165
                                $class = static::class;
1✔
166
                                throw new Nette\UnexpectedValueException("Method $class::$method() did not return or create the desired component.");
1✔
167
                        }
168

169
                        return $component;
1✔
170
                }
171

172
                return null;
1✔
173
        }
174

175

176
        /**
177
         * Returns all immediate child components.
178
         * @return array<int|string,IComponent>
179
         */
180
        final public function getComponents(): iterable
181
        {
182
                $filterType = func_get_args()[1] ?? null;
1✔
183
                if (func_get_args()[0] ?? null) { // back compatibility
1✔
184
                        $iterator = new RecursiveComponentIterator($this->components);
1✔
185
                        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
1✔
186
                        if ($filterType) {
1✔
187
                                $iterator = new \CallbackFilterIterator($iterator, fn($item) => $item instanceof $filterType);
1✔
188
                        }
189
                        return $iterator;
1✔
190
                }
191

192
                return $filterType
1✔
193
                        ? array_filter($this->components, fn($item) => $item instanceof $filterType)
1✔
194
                        : $this->components;
1✔
195
        }
196

197

198
        /**
199
         * Retrieves the entire hierarchy of components, including all nested child components (depth-first).
200
         * @return list<IComponent>
201
         */
202
        final public function getComponentTree(): array
203
        {
204
                $res = [];
1✔
205
                foreach ($this->components as $component) {
1✔
206
                        $res[] = $component;
1✔
207
                        if ($component instanceof self) {
1✔
208
                                $res = array_merge($res, $component->getComponentTree());
1✔
209
                        }
210
                }
211
                return $res;
1✔
212
        }
213

214

215
        /**
216
         * Validates a child component before it's added to the container.
217
         * Descendant classes can override this to implement custom validation logic.
218
         * @throws Nette\InvalidStateException
219
         */
220
        protected function validateChildComponent(IComponent $child): void
1✔
221
        {
222
        }
1✔
223

224

225
        /********************* cloneable, serializable ****************d*g**/
226

227

228
        /**
229
         * Handles object cloning. Clones all child components and re-sets their parents.
230
         */
231
        public function __clone()
232
        {
233
                if ($this->components) {
1✔
234
                        $oldMyself = reset($this->components)->getParent();
1✔
235
                        assert($oldMyself instanceof self);
236
                        $oldMyself->cloning = $this;
1✔
237
                        foreach ($this->components as $name => $component) {
1✔
238
                                $this->components[$name] = clone $component;
1✔
239
                        }
240

241
                        $oldMyself->cloning = null;
1✔
242
                }
243

244
                parent::__clone();
1✔
245
        }
1✔
246

247

248
        /**
249
         * Is container cloning now?
250
         * @internal
251
         */
252
        final public function _isCloning(): ?self
253
        {
254
                return $this->cloning;
1✔
255
        }
256
}
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