PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.1.0
Defender Security – Malware Scanner, Login Security & Firewall v6.1.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / lib / packages / DI / Container.php

Container.php in Defender Security – Malware Scanner, Login Security & Firewall 6.1.0, at lib/packages/DI/Container.php

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