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

nette / component-model / 21197986702

21 Jan 2026 05:06AM UTC coverage: 86.425% (+0.2%) from 86.256%
21197986702

push

github

dg
added CLAUDE.md

191 of 221 relevant lines covered (86.43%)

0.86 hits per line

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

85.23
/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 strval;
14

15

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

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

27

28
        /********************* interface IContainer ****************d*g**/
29

30

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

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

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

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

59
                // user checking
60
                $this->validateChildComponent($component);
1✔
61

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

69
                                $tmp[$k] = $v;
1✔
70
                        }
71

72
                        $this->components = $tmp;
1✔
73
                } else {
74
                        $this->components[$name] = $component;
1✔
75
                }
76

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

84
                return $this;
1✔
85
        }
86

87

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

98
                unset($this->components[$name]);
1✔
99
                $component->setParent(null);
1✔
100
        }
1✔
101

102

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

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

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

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

130
                        } elseif ($component instanceof IContainer) {
1✔
131
                                return $component->getComponent($parts[1], $throw);
1✔
132

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

144
                return null;
×
145
        }
146

147

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

166
                        return $component;
1✔
167
                }
168

169
                return null;
1✔
170
        }
171

172

173
        /**
174
         * Returns all immediate child components.
175
         * @return array<int|string, IComponent>
176
         */
177
        final public function getComponents(): array
178
        {
179
                return $this->components;
1✔
180
        }
181

182

183
        /**
184
         * Retrieves the entire hierarchy of components, including all nested child components (depth-first).
185
         * @return list<IComponent>
186
         */
187
        final public function getComponentTree(): array
188
        {
189
                $res = [];
1✔
190
                foreach ($this->components as $component) {
1✔
191
                        $res[] = $component;
1✔
192
                        if ($component instanceof self) {
1✔
193
                                $res = array_merge($res, $component->getComponentTree());
1✔
194
                        }
195
                }
196
                return $res;
1✔
197
        }
198

199

200
        /**
201
         * Validates a child component before it's added to the container.
202
         * Descendant classes can override this to implement custom validation logic.
203
         * @throws Nette\InvalidStateException
204
         */
205
        protected function validateChildComponent(IComponent $child): void
1✔
206
        {
207
        }
1✔
208

209

210
        /********************* cloneable, serializable ****************d*g**/
211

212

213
        /**
214
         * Handles object cloning. Clones all child components and re-sets their parents.
215
         */
216
        public function __clone()
217
        {
218
                if ($this->components) {
1✔
219
                        $oldMyself = reset($this->components)->getParent();
1✔
220
                        assert($oldMyself instanceof self);
221
                        $oldMyself->cloning = $this;
1✔
222
                        foreach ($this->components as $name => $component) {
1✔
223
                                $this->components[$name] = clone $component;
1✔
224
                        }
225

226
                        $oldMyself->cloning = null;
1✔
227
                }
228

229
                parent::__clone();
1✔
230
        }
1✔
231

232

233
        /**
234
         * Is container cloning now?
235
         * @internal
236
         */
237
        final public function _isCloning(): ?self
238
        {
239
                return $this->cloning;
1✔
240
        }
241
}
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

© 2026 Coveralls, Inc