PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.1
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Dependencies / DI / Container.php

Container.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.1, at includes/Dependencies/DI/Container.php

422 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:ignoreFile -- Bundled third-party (Mozart) dependency; exempt from plugin coding standards.
3
4 declare(strict_types=1);
5
6 namespace WPDeveloper\BetterDocs\Dependencies\DI;
7
8 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Definition;
9 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Exception\InvalidDefinition;
10 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\FactoryDefinition;
11 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Helper\DefinitionHelper;
12 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\InstanceDefinition;
13 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\ObjectDefinition;
14 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Resolver\DefinitionResolver;
15 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Resolver\ResolverDispatcher;
16 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Source\DefinitionArray;
17 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Source\MutableDefinitionSource;
18 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Source\ReflectionBasedAutowiring;
19 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Source\SourceChain;
20 use WPDeveloper\BetterDocs\Dependencies\DI\Invoker\DefinitionParameterResolver;
21 use WPDeveloper\BetterDocs\Dependencies\DI\Proxy\ProxyFactory;
22 use InvalidArgumentException;
23 use WPDeveloper\BetterDocs\Dependencies\Invoker\Invoker;
24 use WPDeveloper\BetterDocs\Dependencies\Invoker\InvokerInterface;
25 use WPDeveloper\BetterDocs\Dependencies\Invoker\ParameterResolver\AssociativeArrayResolver;
26 use WPDeveloper\BetterDocs\Dependencies\Invoker\ParameterResolver\Container\TypeHintContainerResolver;
27 use WPDeveloper\BetterDocs\Dependencies\Invoker\ParameterResolver\DefaultValueResolver;
28 use WPDeveloper\BetterDocs\Dependencies\Invoker\ParameterResolver\NumericArrayResolver;
29 use WPDeveloper\BetterDocs\Dependencies\Invoker\ParameterResolver\ResolverChain;
30 use WPDeveloper\BetterDocs\Dependencies\Psr\Container\ContainerInterface;
31
32 /**
33 * Dependency Injection Container.
34 *
35 * @api
36 *
37 * @author Matthieu Napoli <matthieu@mnapoli.fr>
38 */
39 class Container implements ContainerInterface, FactoryInterface, InvokerInterface
40 {
41 /**
42 * Map of entries that are already resolved.
43 * @var array
44 */
45 protected $resolvedEntries = [];
46
47 /**
48 * @var MutableDefinitionSource
49 */
50 private $definitionSource;
51
52 /**
53 * @var DefinitionResolver
54 */
55 private $definitionResolver;
56
57 /**
58 * Map of definitions that are already fetched (local cache).
59 *
60 * @var (Definition|null)[]
61 */
62 private $fetchedDefinitions = [];
63
64 /**
65 * Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
66 * @var array
67 */
68 protected $entriesBeingResolved = [];
69
70 /**
71 * @var InvokerInterface|null
72 */
73 private $invoker;
74
75 /**
76 * Container that wraps this container. If none, points to $this.
77 *
78 * @var ContainerInterface
79 */
80 protected $delegateContainer;
81
82 /**
83 * @var ProxyFactory
84 */
85 protected $proxyFactory;
86
87 /**
88 * Use `$container = new Container()` if you want a container with the default configuration.
89 *
90 * If you want to customize the container's behavior, you are discouraged to create and pass the
91 * dependencies yourself, the ContainerBuilder class is here to help you instead.
92 *
93 * @see ContainerBuilder
94 *
95 * @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
96 */
97 public function __construct(
98 ?MutableDefinitionSource $definitionSource = null,
99 ?ProxyFactory $proxyFactory = null,
100 ?ContainerInterface $wrapperContainer = null
101 ) {
102 $this->delegateContainer = $wrapperContainer ?: $this;
103
104 $this->definitionSource = $definitionSource ?: $this->createDefaultDefinitionSource();
105 $this->proxyFactory = $proxyFactory ?: new ProxyFactory(false);
106 $this->definitionResolver = new ResolverDispatcher($this->delegateContainer, $this->proxyFactory);
107
108 // Auto-register the container
109 $this->resolvedEntries = [
110 self::class => $this,
111 FactoryInterface::class => $this,
112 InvokerInterface::class => $this,
113 ContainerInterface::class => $this,
114 ];
115 }
116
117 /**
118 * Returns an entry of the container by its name.
119 *
120 * @param string $name Entry name or a class name.
121 *
122 * @throws InvalidArgumentException The name parameter must be of type string.
123 * @throws DependencyException Error while resolving the entry.
124 * @throws NotFoundException No entry found for the given name.
125 * @return mixed
126 */
127 public function get($name)
128 {
129 // If the entry is already resolved we return it
130 if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {
131 return $this->resolvedEntries[$name];
132 }
133
134 $definition = $this->getDefinition($name);
135 if (! $definition) {
136 throw new NotFoundException("No entry or class found for '$name'");
137 }
138
139 $value = $this->resolveDefinition($definition);
140
141 $this->resolvedEntries[$name] = $value;
142
143 return $value;
144 }
145
146 /**
147 * @param string $name
148 *
149 * @return Definition|null
150 */
151 private function getDefinition($name)
152 {
153 // Local cache that avoids fetching the same definition twice
154 if (!array_key_exists($name, $this->fetchedDefinitions)) {
155 $this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
156 }
157
158 return $this->fetchedDefinitions[$name];
159 }
160
161 /**
162 * Build an entry of the container by its name.
163 *
164 * This method behave like get() except resolves the entry again every time.
165 * For example if the entry is a class then a new instance will be created each time.
166 *
167 * This method makes the container behave like a factory.
168 *
169 * @param string $name Entry name or a class name.
170 * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
171 * to specific values. Parameters not defined in this array will be resolved using
172 * the container.
173 *
174 * @throws InvalidArgumentException The name parameter must be of type string.
175 * @throws DependencyException Error while resolving the entry.
176 * @throws NotFoundException No entry found for the given name.
177 * @return mixed
178 */
179 public function make($name, array $parameters = [])
180 {
181 if (! is_string($name)) {
182 throw new InvalidArgumentException(sprintf(
183 'The name parameter must be of type string, %s given',
184 is_object($name) ? get_class($name) : gettype($name)
185 ));
186 }
187
188 $definition = $this->getDefinition($name);
189 if (! $definition) {
190 // If the entry is already resolved we return it
191 if (array_key_exists($name, $this->resolvedEntries)) {
192 return $this->resolvedEntries[$name];
193 }
194
195 throw new NotFoundException("No entry or class found for '$name'");
196 }
197
198 return $this->resolveDefinition($definition, $parameters);
199 }
200
201 /**
202 * Test if the container can provide something for the given name.
203 *
204 * @param string $name Entry name or a class name.
205 *
206 * @throws InvalidArgumentException The name parameter must be of type string.
207 * @return bool
208 */
209 public function has($name)
210 {
211 if (! is_string($name)) {
212 throw new InvalidArgumentException(sprintf(
213 'The name parameter must be of type string, %s given',
214 is_object($name) ? get_class($name) : gettype($name)
215 ));
216 }
217
218 if (array_key_exists($name, $this->resolvedEntries)) {
219 return true;
220 }
221
222 $definition = $this->getDefinition($name);
223 if ($definition === null) {
224 return false;
225 }
226
227 return $this->definitionResolver->isResolvable($definition);
228 }
229
230 /**
231 * Inject all dependencies on an existing instance.
232 *
233 * @param object $instance Object to perform injection upon
234 * @throws InvalidArgumentException
235 * @throws DependencyException Error while injecting dependencies
236 * @return object $instance Returns the same instance
237 */
238 public function injectOn($instance)
239 {
240 if (!$instance) {
241 return $instance;
242 }
243
244 $objectDefinition = $this->definitionSource->getDefinition(get_class($instance));
245 if (! $objectDefinition instanceof ObjectDefinition) {
246 return $instance;
247 }
248
249 $definition = new InstanceDefinition($instance, $objectDefinition);
250
251 $this->definitionResolver->resolve($definition);
252
253 return $instance;
254 }
255
256 /**
257 * Call the given function using the given parameters.
258 *
259 * Missing parameters will be resolved from the container.
260 *
261 * @param callable $callable Function to call.
262 * @param array $parameters Parameters to use. Can be indexed by the parameter names
263 * or not indexed (same order as the parameters).
264 * The array can also contain WPDeveloper\BetterDocs\Dependencies\DI definitions, e.g. WPDeveloper\BetterDocs\Dependencies\DI\get().
265 *
266 * @return mixed Result of the function.
267 */
268 public function call($callable, array $parameters = [])
269 {
270 return $this->getInvoker()->call($callable, $parameters);
271 }
272
273 /**
274 * Define an object or a value in the container.
275 *
276 * @param string $name Entry name
277 * @param mixed|DefinitionHelper $value Value, use definition helpers to define objects
278 */
279 public function set(string $name, $value)
280 {
281 if ($value instanceof DefinitionHelper) {
282 $value = $value->getDefinition($name);
283 } elseif ($value instanceof \Closure) {
284 $value = new FactoryDefinition($name, $value);
285 }
286
287 if ($value instanceof Definition) {
288 $this->setDefinition($name, $value);
289 } else {
290 $this->resolvedEntries[$name] = $value;
291 }
292 }
293
294 /**
295 * Get defined container entries.
296 *
297 * @return string[]
298 */
299 public function getKnownEntryNames() : array
300 {
301 $entries = array_unique(array_merge(
302 array_keys($this->definitionSource->getDefinitions()),
303 array_keys($this->resolvedEntries)
304 ));
305 sort($entries);
306
307 return $entries;
308 }
309
310 /**
311 * Get entry debug information.
312 *
313 * @param string $name Entry name
314 *
315 * @throws InvalidDefinition
316 * @throws NotFoundException
317 */
318 public function debugEntry(string $name) : string
319 {
320 $definition = $this->definitionSource->getDefinition($name);
321 if ($definition instanceof Definition) {
322 return (string) $definition;
323 }
324
325 if (array_key_exists($name, $this->resolvedEntries)) {
326 return $this->getEntryType($this->resolvedEntries[$name]);
327 }
328
329 throw new NotFoundException("No entry or class found for '$name'");
330 }
331
332 /**
333 * Get formatted entry type.
334 *
335 * @param mixed $entry
336 */
337 private function getEntryType($entry) : string
338 {
339 if (is_object($entry)) {
340 return sprintf("Object (\n class = %s\n)", get_class($entry));
341 }
342
343 if (is_array($entry)) {
344 return preg_replace(['/^array \(/', '/\)$/'], ['[', ']'], var_export($entry, true));
345 }
346
347 if (is_string($entry)) {
348 return sprintf('Value (\'%s\')', $entry);
349 }
350
351 if (is_bool($entry)) {
352 return sprintf('Value (%s)', $entry === true ? 'true' : 'false');
353 }
354
355 return sprintf('Value (%s)', is_scalar($entry) ? $entry : ucfirst(gettype($entry)));
356 }
357
358 /**
359 * Resolves a definition.
360 *
361 * Checks for circular dependencies while resolving the definition.
362 *
363 * @throws DependencyException Error while resolving the entry.
364 * @return mixed
365 */
366 private function resolveDefinition(Definition $definition, array $parameters = [])
367 {
368 $entryName = $definition->getName();
369
370 // Check if we are already getting this entry -> circular dependency
371 if (isset($this->entriesBeingResolved[$entryName])) {
372 throw new DependencyException("Circular dependency detected while trying to resolve entry '$entryName'");
373 }
374 $this->entriesBeingResolved[$entryName] = true;
375
376 // Resolve the definition
377 try {
378 $value = $this->definitionResolver->resolve($definition, $parameters);
379 } finally {
380 unset($this->entriesBeingResolved[$entryName]);
381 }
382
383 return $value;
384 }
385
386 protected function setDefinition(string $name, Definition $definition)
387 {
388 // Clear existing entry if it exists
389 if (array_key_exists($name, $this->resolvedEntries)) {
390 unset($this->resolvedEntries[$name]);
391 }
392 $this->fetchedDefinitions = []; // Completely clear this local cache
393
394 $this->definitionSource->addDefinition($definition);
395 }
396
397 private function getInvoker() : InvokerInterface
398 {
399 if (! $this->invoker) {
400 $parameterResolver = new ResolverChain([
401 new DefinitionParameterResolver($this->definitionResolver),
402 new NumericArrayResolver,
403 new AssociativeArrayResolver,
404 new DefaultValueResolver,
405 new TypeHintContainerResolver($this->delegateContainer),
406 ]);
407
408 $this->invoker = new Invoker($parameterResolver, $this);
409 }
410
411 return $this->invoker;
412 }
413
414 private function createDefaultDefinitionSource() : SourceChain
415 {
416 $source = new SourceChain([new ReflectionBasedAutowiring]);
417 $source->setMutableDefinitionSource(new DefinitionArray([], new ReflectionBasedAutowiring));
418
419 return $source;
420 }
421 }
422