• 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

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

14

15
/**
16
 * ComponentContainer is default implementation of IContainer.
17
 *
18
 * @template T of IComponent
19
 * @implements IContainer<T>
20
 * @property-read iterable $components
21
 */
22
class Container extends Component implements IContainer
23
{
24
        private const NameRegexp = '#^[a-zA-Z0-9_]+$#D';
25

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

30

31
        /********************* interface IContainer ****************d*g**/
32

33

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

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

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

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

63
                        $obj = $obj->getParent();
1✔
64
                } while ($obj !== null);
1✔
65

66
                // user checking
67
                $this->validateChildComponent($component);
1✔
68

69
                if (isset($this->components[$insertBefore])) {
1✔
70
                        $tmp = [];
1✔
71
                        foreach ($this->components as $k => $v) {
1✔
72
                                if ((string) $k === $insertBefore) {
1✔
73
                                        $tmp[$name] = $component;
1✔
74
                                }
75

76
                                $tmp[$k] = $v;
1✔
77
                        }
78

79
                        $this->components = $tmp;
1✔
80
                } else {
81
                        $this->components[$name] = $component;
1✔
82
                }
83

84
                try {
85
                        $component->setParent($this, $name);
1✔
86
                } catch (\Throwable $e) {
1✔
87
                        unset($this->components[$name]); // undo
1✔
88
                        throw $e;
1✔
89
                }
90

91
                return $this;
1✔
92
        }
93

94

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

105
                unset($this->components[$name]);
1✔
106
                $component->setParent(null);
1✔
107
        }
1✔
108

109

110
        /**
111
         * Returns component specified by name or path.
112
         * @param  bool  $throw  throw exception if component doesn't exist?
113
         * @return ($throw is true ? IComponent : ?IComponent)
114
         */
115
        final public function getComponent(string $name, bool $throw = true): ?IComponent
1✔
116
        {
117
                [$name] = $parts = explode(self::NameSeparator, $name, 2);
1✔
118

119
                if (!isset($this->components[$name])) {
1✔
120
                        if (!preg_match(self::NameRegexp, $name)) {
1✔
121
                                if ($throw) {
×
122
                                        throw new Nette\InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
×
123
                                }
124

125
                                return null;
×
126
                        }
127

128
                        $component = $this->createComponent($name);
1✔
129
                        if ($component && !isset($this->components[$name])) {
1✔
130
                                $this->addComponent($component, $name);
1✔
131
                        }
132
                }
133

134
                $component = $this->components[$name] ?? null;
1✔
135
                if ($component !== null) {
1✔
136
                        if (!isset($parts[1])) {
1✔
137
                                return $component;
1✔
138

139
                        } elseif ($component instanceof IContainer) {
1✔
140
                                return $component->getComponent($parts[1], $throw);
1✔
141

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

153
                return null;
×
154
        }
155

156

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

175
                        return $component;
1✔
176
                }
177

178
                return null;
1✔
179
        }
180

181

182
        /**
183
         * Returns array of components.
184
         * @return iterable<int|string,IComponent>
185
         */
186
        final public function getComponents(): iterable
187
        {
188
                $filterType = func_get_args()[1] ?? null;
1✔
189
                if (func_get_args()[0] ?? null) {
1✔
190
                        $iterator = new RecursiveComponentIterator($this->components);
1✔
191
                        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
1✔
192
                        if ($filterType) {
1✔
193
                                $iterator = new \CallbackFilterIterator($iterator, fn($item) => $item instanceof $filterType);
1✔
194
                        }
195
                        return $iterator;
1✔
196
                }
197

198
                return $filterType === null
1✔
199
                        ? $this->components
1✔
200
                        : array_filter($this->components, fn($item) => $item instanceof $filterType);
1✔
201
        }
202

203

204
        /**
205
         * Iterates over descendant components in depth.
206
         * @return \Iterator<int|string,IComponent>
207
         */
208
        final public function findComponents(?string $filterType = null): \Iterator
1✔
209
        {
210
                foreach ($this->components as $name => $component) {
1✔
211
                        if (!$filterType || $component instanceof $filterType) {
1✔
212
                                yield $name => $component;
1✔
213
                        }
214
                        if ($component instanceof self) {
1✔
215
                                yield from $component->findComponents($filterType);
1✔
216
                        }
217
                }
218
        }
1✔
219

220

221
        /**
222
         * Descendant can override this method to disallow insert a child by throwing an Nette\InvalidStateException.
223
         * @throws Nette\InvalidStateException
224
         */
225
        protected function validateChildComponent(IComponent $child): void
1✔
226
        {
227
        }
1✔
228

229

230
        /********************* cloneable, serializable ****************d*g**/
231

232

233
        /**
234
         * Object cloning.
235
         */
236
        public function __clone()
237
        {
238
                if ($this->components) {
1✔
239
                        $oldMyself = reset($this->components)->getParent();
1✔
240
                        assert($oldMyself instanceof self);
241
                        $oldMyself->cloning = $this;
1✔
242
                        foreach ($this->components as $name => $component) {
1✔
243
                                $this->components[$name] = clone $component;
1✔
244
                        }
245

246
                        $oldMyself->cloning = null;
1✔
247
                }
248

249
                parent::__clone();
1✔
250
        }
1✔
251

252

253
        /**
254
         * Is container cloning now?
255
         * @internal
256
         */
257
        final public function _isCloning(): ?self
258
        {
259
                return $this->cloning;
1✔
260
        }
261
}
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