PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
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.2.6, at includes/Dependencies/DI/Container.php

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