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

nette / component-model / 27921006080

21 Jun 2026 11:31PM UTC coverage: 85.981% (-0.8%) from 86.765%
27921006080

push

github

dg
added CLAUDE.md

184 of 214 relevant lines covered (85.98%)

0.86 hits per line

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

83.87
/src/ComponentModel/Container.php
1
<?php declare(strict_types=1);
1✔
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
namespace Nette\ComponentModel;
9

10
use Nette;
11
use function strval;
12

13

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

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

25

26
        /********************* interface IContainer ****************d*g**/
27

28

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

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

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

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

57
                // user checking
58
                $this->validateChildComponent($component);
1✔
59

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

67
                                $tmp[$k] = $v;
1✔
68
                        }
69

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

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

82
                return $this;
1✔
83
        }
84

85

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

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

100

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

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

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

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

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

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

142
                return null;
×
143
        }
144

145

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

164
                        return $component;
1✔
165
                }
166

167
                return null;
1✔
168
        }
169

170

171
        /**
172
         * Returns all immediate child components.
173
         * @return IComponent[]
174
         */
175
        final public function getComponents(): array
176
        {
177
                if (func_get_args()[0] ?? null) {
1✔
178
                        throw new Nette\DeprecatedException(__METHOD__ . '() with recursive flag is deprecated. Use getComponentTree() instead.');
×
179
                }
180
                if (func_get_args()[1] ?? null) {
1✔
181
                        throw new Nette\DeprecatedException('Using Container::getComponents() with filter type is deprecated.');
×
182
                }
183
                return $this->components;
1✔
184
        }
185

186

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

203

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

213

214
        /********************* cloneable, serializable ****************d*g**/
215

216

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

230
                        $oldMyself->cloning = null;
1✔
231
                }
232

233
                parent::__clone();
1✔
234
        }
1✔
235

236

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