Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
150 lars 3
declare(strict_types=1);
4
 
148 lars 5
namespace Doctrine\Instantiator;
6
 
7
use ArrayIterator;
8
use Doctrine\Instantiator\Exception\ExceptionInterface;
9
use Doctrine\Instantiator\Exception\InvalidArgumentException;
10
use Doctrine\Instantiator\Exception\UnexpectedValueException;
11
use Exception;
12
use ReflectionClass;
13
use ReflectionException;
14
use Serializable;
15
 
16
use function class_exists;
17
use function enum_exists;
18
use function is_subclass_of;
19
use function restore_error_handler;
20
use function set_error_handler;
21
use function sprintf;
22
use function strlen;
23
use function unserialize;
24
 
25
final class Instantiator implements InstantiatorInterface
26
{
27
    /**
28
     * Markers used internally by PHP to define whether {@see \unserialize} should invoke
29
     * the method {@see \Serializable::unserialize()} when dealing with classes implementing
30
     * the {@see \Serializable} interface.
31
     *
32
     * @deprecated This constant will be private in 2.0
33
     */
150 lars 34
    private const SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
35
    private const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
148 lars 36
 
37
    /**
38
     * Used to instantiate specific classes, indexed by class name.
39
     *
40
     * @var callable[]
41
     */
150 lars 42
    private static array $cachedInstantiators = [];
148 lars 43
 
44
    /**
45
     * Array of objects that can directly be cloned, indexed by class name.
46
     *
47
     * @var object[]
48
     */
150 lars 49
    private static array $cachedCloneables = [];
148 lars 50
 
51
    /**
52
     * @phpstan-param class-string<T> $className
53
     *
54
     * @phpstan-return T
55
     *
56
     * @throws ExceptionInterface
57
     *
58
     * @template T of object
59
     */
150 lars 60
    public function instantiate(string $className): object
148 lars 61
    {
62
        if (isset(self::$cachedCloneables[$className])) {
63
            /** @phpstan-var T */
64
            $cachedCloneable = self::$cachedCloneables[$className];
65
 
66
            return clone $cachedCloneable;
67
        }
68
 
69
        if (isset(self::$cachedInstantiators[$className])) {
70
            $factory = self::$cachedInstantiators[$className];
71
 
72
            return $factory();
73
        }
74
 
75
        return $this->buildAndCacheFromFactory($className);
76
    }
77
 
78
    /**
79
     * Builds the requested object and caches it in static properties for performance
80
     *
81
     * @phpstan-param class-string<T> $className
82
     *
83
     * @phpstan-return T
84
     *
85
     * @template T of object
86
     */
150 lars 87
    private function buildAndCacheFromFactory(string $className): object
148 lars 88
    {
89
        $factory  = self::$cachedInstantiators[$className] = $this->buildFactory($className);
90
        $instance = $factory();
91
 
92
        if ($this->isSafeToClone(new ReflectionClass($instance))) {
93
            self::$cachedCloneables[$className] = clone $instance;
94
        }
95
 
96
        return $instance;
97
    }
98
 
99
    /**
100
     * Builds a callable capable of instantiating the given $className without
101
     * invoking its constructor.
102
     *
103
     * @phpstan-param class-string<T> $className
104
     *
105
     * @phpstan-return callable(): T
106
     *
107
     * @throws InvalidArgumentException
108
     * @throws UnexpectedValueException
109
     * @throws ReflectionException
110
     *
111
     * @template T of object
112
     */
113
    private function buildFactory(string $className): callable
114
    {
115
        $reflectionClass = $this->getReflectionClass($className);
116
 
117
        if ($this->isInstantiableViaReflection($reflectionClass)) {
118
            return [$reflectionClass, 'newInstanceWithoutConstructor'];
119
        }
120
 
121
        $serializedString = sprintf(
122
            '%s:%d:"%s":0:{}',
123
            is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
124
            strlen($className),
150 lars 125
            $className,
148 lars 126
        );
127
 
128
        $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
129
 
150 lars 130
        return static fn () => unserialize($serializedString);
148 lars 131
    }
132
 
133
    /**
134
     * @phpstan-param class-string<T> $className
135
     *
136
     * @phpstan-return ReflectionClass<T>
137
     *
138
     * @throws InvalidArgumentException
139
     * @throws ReflectionException
140
     *
141
     * @template T of object
142
     */
143
    private function getReflectionClass(string $className): ReflectionClass
144
    {
145
        if (! class_exists($className)) {
146
            throw InvalidArgumentException::fromNonExistingClass($className);
147
        }
148
 
150 lars 149
        if (enum_exists($className, false)) {
148 lars 150
            throw InvalidArgumentException::fromEnum($className);
151
        }
152
 
153
        $reflection = new ReflectionClass($className);
154
 
155
        if ($reflection->isAbstract()) {
156
            throw InvalidArgumentException::fromAbstractClass($reflection);
157
        }
158
 
159
        return $reflection;
160
    }
161
 
162
    /**
163
     * @phpstan-param ReflectionClass<T> $reflectionClass
164
     *
165
     * @throws UnexpectedValueException
166
     *
167
     * @template T of object
168
     */
169
    private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void
170
    {
171
        set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool {
172
            $error = UnexpectedValueException::fromUncleanUnSerialization(
173
                $reflectionClass,
174
                $message,
175
                $code,
176
                $file,
150 lars 177
                $line,
148 lars 178
            );
179
 
180
            return true;
181
        });
182
 
183
        try {
184
            $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
185
        } finally {
186
            restore_error_handler();
187
        }
188
 
189
        if ($error) {
190
            throw $error;
191
        }
192
    }
193
 
194
    /**
195
     * @phpstan-param ReflectionClass<T> $reflectionClass
196
     *
197
     * @throws UnexpectedValueException
198
     *
199
     * @template T of object
200
     */
201
    private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void
202
    {
203
        try {
204
            unserialize($serializedString);
205
        } catch (Exception $exception) {
206
            throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
207
        }
208
    }
209
 
210
    /**
211
     * @phpstan-param ReflectionClass<T> $reflectionClass
212
     *
213
     * @template T of object
214
     */
215
    private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool
216
    {
217
        return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
218
    }
219
 
220
    /**
221
     * Verifies whether the given class is to be considered internal
222
     *
223
     * @phpstan-param ReflectionClass<T> $reflectionClass
224
     *
225
     * @template T of object
226
     */
227
    private function hasInternalAncestors(ReflectionClass $reflectionClass): bool
228
    {
229
        do {
230
            if ($reflectionClass->isInternal()) {
231
                return true;
232
            }
233
 
234
            $reflectionClass = $reflectionClass->getParentClass();
235
        } while ($reflectionClass);
236
 
237
        return false;
238
    }
239
 
240
    /**
241
     * Checks if a class is cloneable
242
     *
243
     * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects.
244
     *
245
     * @phpstan-param ReflectionClass<T> $reflectionClass
246
     *
247
     * @template T of object
248
     */
249
    private function isSafeToClone(ReflectionClass $reflectionClass): bool
250
    {
251
        return $reflectionClass->isCloneable()
252
            && ! $reflectionClass->hasMethod('__clone')
253
            && ! $reflectionClass->isSubclassOf(ArrayIterator::class);
254
    }
255
}