Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

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