Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace DI\Definition\Resolver;
6
 
7
use DI\Definition\Definition;
8
use DI\Definition\Exception\InvalidDefinition;
9
use DI\Definition\ObjectDefinition;
10
use DI\Definition\ObjectDefinition\PropertyInjection;
11
use DI\DependencyException;
12
use DI\Proxy\ProxyFactory;
13
use Exception;
14
use ProxyManager\Proxy\LazyLoadingInterface;
15
use Psr\Container\NotFoundExceptionInterface;
16
use ReflectionClass;
17
use ReflectionProperty;
18
 
19
/**
20
 * Create objects based on an object definition.
21
 *
22
 * @template-implements DefinitionResolver<ObjectDefinition>
23
 *
24
 * @since 4.0
25
 * @author Matthieu Napoli <matthieu@mnapoli.fr>
26
 */
27
class ObjectCreator implements DefinitionResolver
28
{
29
    private ParameterResolver $parameterResolver;
30
 
31
    /**
32
     * @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
33
     * @param ProxyFactory       $proxyFactory       Used to create proxies for lazy injections.
34
     */
35
    public function __construct(
36
        private DefinitionResolver $definitionResolver,
37
        private ProxyFactory $proxyFactory
38
    ) {
39
        $this->parameterResolver = new ParameterResolver($definitionResolver);
40
    }
41
 
42
    /**
43
     * Resolve a class definition to a value.
44
     *
45
     * This will create a new instance of the class using the injections points defined.
46
     *
47
     * @param ObjectDefinition $definition
48
     */
49
    public function resolve(Definition $definition, array $parameters = []) : ?object
50
    {
51
        // Lazy?
52
        if ($definition->isLazy()) {
53
            return $this->createProxy($definition, $parameters);
54
        }
55
 
56
        return $this->createInstance($definition, $parameters);
57
    }
58
 
59
    /**
60
     * The definition is not resolvable if the class is not instantiable (interface or abstract)
61
     * or if the class doesn't exist.
62
     *
63
     * @param ObjectDefinition $definition
64
     */
65
    public function isResolvable(Definition $definition, array $parameters = []) : bool
66
    {
67
        return $definition->isInstantiable();
68
    }
69
 
70
    /**
71
     * Returns a proxy instance.
72
     */
73
    private function createProxy(ObjectDefinition $definition, array $parameters) : LazyLoadingInterface
74
    {
75
        /** @var class-string $className */
76
        $className = $definition->getClassName();
77
 
78
        return $this->proxyFactory->createProxy(
79
            $className,
80
            function (& $wrappedObject, $proxy, $method, $params, & $initializer) use ($definition, $parameters) {
81
                $wrappedObject = $this->createInstance($definition, $parameters);
82
                $initializer = null; // turning off further lazy initialization
83
 
84
                return true;
85
            }
86
        );
87
    }
88
 
89
    /**
90
     * Creates an instance of the class and injects dependencies..
91
     *
92
     * @param array $parameters Optional parameters to use to create the instance.
93
     *
94
     * @throws DependencyException
95
     * @throws InvalidDefinition
96
     */
97
    private function createInstance(ObjectDefinition $definition, array $parameters) : object
98
    {
99
        // Check that the class is instantiable
100
        if (! $definition->isInstantiable()) {
101
            // Check that the class exists
102
            if (! $definition->classExists()) {
103
                throw InvalidDefinition::create($definition, sprintf(
104
                    'Entry "%s" cannot be resolved: the class doesn\'t exist',
105
                    $definition->getName()
106
                ));
107
            }
108
 
109
            throw InvalidDefinition::create($definition, sprintf(
110
                'Entry "%s" cannot be resolved: the class is not instantiable',
111
                $definition->getName()
112
            ));
113
        }
114
 
115
        /** @psalm-var class-string $classname */
116
        $classname = $definition->getClassName();
117
        $classReflection = new ReflectionClass($classname);
118
 
119
        $constructorInjection = $definition->getConstructorInjection();
120
 
121
        /** @psalm-suppress InvalidCatch */
122
        try {
123
            $args = $this->parameterResolver->resolveParameters(
124
                $constructorInjection,
125
                $classReflection->getConstructor(),
126
                $parameters
127
            );
128
 
129
            $object = new $classname(...$args);
130
 
131
            $this->injectMethodsAndProperties($object, $definition);
132
        } catch (NotFoundExceptionInterface $e) {
133
            throw new DependencyException(sprintf(
134
                'Error while injecting dependencies into %s: %s',
135
                $classReflection->getName(),
136
                $e->getMessage()
137
            ), 0, $e);
138
        } catch (InvalidDefinition $e) {
139
            throw InvalidDefinition::create($definition, sprintf(
140
                'Entry "%s" cannot be resolved: %s',
141
                $definition->getName(),
142
                $e->getMessage()
143
            ));
144
        }
145
 
146
        return $object;
147
    }
148
 
149
    protected function injectMethodsAndProperties(object $object, ObjectDefinition $objectDefinition) : void
150
    {
151
        // Property injections
152
        foreach ($objectDefinition->getPropertyInjections() as $propertyInjection) {
153
            $this->injectProperty($object, $propertyInjection);
154
        }
155
 
156
        // Method injections
157
        foreach ($objectDefinition->getMethodInjections() as $methodInjection) {
158
            $methodReflection = new \ReflectionMethod($object, $methodInjection->getMethodName());
159
            $args = $this->parameterResolver->resolveParameters($methodInjection, $methodReflection);
160
 
161
            $methodReflection->invokeArgs($object, $args);
162
        }
163
    }
164
 
165
    /**
166
     * Inject dependencies into properties.
167
     *
168
     * @param object            $object            Object to inject dependencies into
169
     * @param PropertyInjection $propertyInjection Property injection definition
170
     *
171
     * @throws DependencyException
172
     */
173
    private function injectProperty(object $object, PropertyInjection $propertyInjection) : void
174
    {
175
        $propertyName = $propertyInjection->getPropertyName();
176
 
177
        $value = $propertyInjection->getValue();
178
 
179
        if ($value instanceof Definition) {
180
            try {
181
                $value = $this->definitionResolver->resolve($value);
182
            } catch (DependencyException $e) {
183
                throw $e;
184
            } catch (Exception $e) {
185
                throw new DependencyException(sprintf(
186
                    'Error while injecting in %s::%s. %s',
187
                    $object::class,
188
                    $propertyName,
189
                    $e->getMessage()
190
                ), 0, $e);
191
            }
192
        }
193
 
194
        self::setPrivatePropertyValue($propertyInjection->getClassName(), $object, $propertyName, $value);
195
    }
196
 
197
    public static function setPrivatePropertyValue(?string $className, $object, string $propertyName, mixed $propertyValue) : void
198
    {
199
        $className = $className ?: $object::class;
200
 
201
        $property = new ReflectionProperty($className, $propertyName);
202
        if (! $property->isPublic() && \PHP_VERSION_ID < 80100) {
203
            $property->setAccessible(true);
204
        }
205
        $property->setValue($object, $propertyValue);
206
    }
207
}