3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\Instantiator;
23 use Doctrine\Instantiator\Exception\InvalidArgumentException;
24 use Doctrine\Instantiator\Exception\UnexpectedValueException;
31 * @author Marco Pivetta <ocramius@gmail.com>
33 final class Instantiator implements InstantiatorInterface
36 * Markers used internally by PHP to define whether {@see \unserialize} should invoke
37 * the method {@see \Serializable::unserialize()} when dealing with classes implementing
38 * the {@see \Serializable} interface.
40 const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C';
41 const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
44 * @var \Closure[] of {@see \Closure} instances used to instantiate specific classes
46 private static $cachedInstantiators = array();
49 * @var object[] of objects that can directly be cloned
51 private static $cachedCloneables = array();
56 public function instantiate($className)
58 if (isset(self::$cachedCloneables[$className])) {
59 return clone self::$cachedCloneables[$className];
62 if (isset(self::$cachedInstantiators[$className])) {
63 $factory = self::$cachedInstantiators[$className];
68 return $this->buildAndCacheFromFactory($className);
72 * Builds the requested object and caches it in static properties for performance
74 * @param string $className
78 private function buildAndCacheFromFactory($className)
80 $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className);
81 $instance = $factory();
83 if ($this->isSafeToClone(new ReflectionClass($instance))) {
84 self::$cachedCloneables[$className] = clone $instance;
91 * Builds a {@see \Closure} capable of instantiating the given $className without
92 * invoking its constructor.
94 * @param string $className
98 private function buildFactory($className)
100 $reflectionClass = $this->getReflectionClass($className);
102 if ($this->isInstantiableViaReflection($reflectionClass)) {
103 return function () use ($reflectionClass) {
104 return $reflectionClass->newInstanceWithoutConstructor();
108 $serializedString = sprintf(
110 $this->getSerializationFormat($reflectionClass),
115 $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
117 return function () use ($serializedString) {
118 return unserialize($serializedString);
123 * @param string $className
125 * @return ReflectionClass
127 * @throws InvalidArgumentException
129 private function getReflectionClass($className)
131 if (! class_exists($className)) {
132 throw InvalidArgumentException::fromNonExistingClass($className);
135 $reflection = new ReflectionClass($className);
137 if ($reflection->isAbstract()) {
138 throw InvalidArgumentException::fromAbstractClass($reflection);
145 * @param ReflectionClass $reflectionClass
146 * @param string $serializedString
148 * @throws UnexpectedValueException
152 private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString)
154 set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
155 $error = UnexpectedValueException::fromUncleanUnSerialization(
164 $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
166 restore_error_handler();
174 * @param ReflectionClass $reflectionClass
175 * @param string $serializedString
177 * @throws UnexpectedValueException
181 private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
184 unserialize($serializedString);
185 } catch (Exception $exception) {
186 restore_error_handler();
188 throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
193 * @param ReflectionClass $reflectionClass
197 private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
199 if (\PHP_VERSION_ID >= 50600) {
200 return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
203 return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
207 * Verifies whether the given class is to be considered internal
209 * @param ReflectionClass $reflectionClass
213 private function hasInternalAncestors(ReflectionClass $reflectionClass)
216 if ($reflectionClass->isInternal()) {
219 } while ($reflectionClass = $reflectionClass->getParentClass());
225 * Verifies if the given PHP version implements the `Serializable` interface serialization
226 * with an incompatible serialization format. If that's the case, use serialization marker
227 * "C" instead of "O".
229 * @link http://news.php.net/php.internals/74654
231 * @param ReflectionClass $reflectionClass
233 * @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
234 * or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
236 private function getSerializationFormat(ReflectionClass $reflectionClass)
238 if ($this->isPhpVersionWithBrokenSerializationFormat()
239 && $reflectionClass->implementsInterface('Serializable')
241 return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
244 return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
248 * Checks whether the current PHP runtime uses an incompatible serialization format
252 private function isPhpVersionWithBrokenSerializationFormat()
254 return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
258 * Checks if a class is cloneable
260 * @param ReflectionClass $reflection
264 private function isSafeToClone(ReflectionClass $reflection)
266 if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
270 // not cloneable if it implements `__clone`, as we want to avoid calling it
271 return ! $reflection->hasMethod('__clone');