Version 1
[yaffs-website] / vendor / doctrine / instantiator / src / Doctrine / Instantiator / Instantiator.php
diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php
new file mode 100644 (file)
index 0000000..6d5b3b6
--- /dev/null
@@ -0,0 +1,273 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Instantiator;
+
+use Closure;
+use Doctrine\Instantiator\Exception\InvalidArgumentException;
+use Doctrine\Instantiator\Exception\UnexpectedValueException;
+use Exception;
+use ReflectionClass;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+final class Instantiator implements InstantiatorInterface
+{
+    /**
+     * Markers used internally by PHP to define whether {@see \unserialize} should invoke
+     * the method {@see \Serializable::unserialize()} when dealing with classes implementing
+     * the {@see \Serializable} interface.
+     */
+    const SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
+    const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
+
+    /**
+     * @var \Closure[] of {@see \Closure} instances used to instantiate specific classes
+     */
+    private static $cachedInstantiators = array();
+
+    /**
+     * @var object[] of objects that can directly be cloned
+     */
+    private static $cachedCloneables = array();
+
+    /**
+     * {@inheritDoc}
+     */
+    public function instantiate($className)
+    {
+        if (isset(self::$cachedCloneables[$className])) {
+            return clone self::$cachedCloneables[$className];
+        }
+
+        if (isset(self::$cachedInstantiators[$className])) {
+            $factory = self::$cachedInstantiators[$className];
+
+            return $factory();
+        }
+
+        return $this->buildAndCacheFromFactory($className);
+    }
+
+    /**
+     * Builds the requested object and caches it in static properties for performance
+     *
+     * @param string $className
+     *
+     * @return object
+     */
+    private function buildAndCacheFromFactory($className)
+    {
+        $factory  = self::$cachedInstantiators[$className] = $this->buildFactory($className);
+        $instance = $factory();
+
+        if ($this->isSafeToClone(new ReflectionClass($instance))) {
+            self::$cachedCloneables[$className] = clone $instance;
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Builds a {@see \Closure} capable of instantiating the given $className without
+     * invoking its constructor.
+     *
+     * @param string $className
+     *
+     * @return Closure
+     */
+    private function buildFactory($className)
+    {
+        $reflectionClass = $this->getReflectionClass($className);
+
+        if ($this->isInstantiableViaReflection($reflectionClass)) {
+            return function () use ($reflectionClass) {
+                return $reflectionClass->newInstanceWithoutConstructor();
+            };
+        }
+
+        $serializedString = sprintf(
+            '%s:%d:"%s":0:{}',
+            $this->getSerializationFormat($reflectionClass),
+            strlen($className),
+            $className
+        );
+
+        $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
+
+        return function () use ($serializedString) {
+            return unserialize($serializedString);
+        };
+    }
+
+    /**
+     * @param string $className
+     *
+     * @return ReflectionClass
+     *
+     * @throws InvalidArgumentException
+     */
+    private function getReflectionClass($className)
+    {
+        if (! class_exists($className)) {
+            throw InvalidArgumentException::fromNonExistingClass($className);
+        }
+
+        $reflection = new ReflectionClass($className);
+
+        if ($reflection->isAbstract()) {
+            throw InvalidArgumentException::fromAbstractClass($reflection);
+        }
+
+        return $reflection;
+    }
+
+    /**
+     * @param ReflectionClass $reflectionClass
+     * @param string          $serializedString
+     *
+     * @throws UnexpectedValueException
+     *
+     * @return void
+     */
+    private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString)
+    {
+        set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
+            $error = UnexpectedValueException::fromUncleanUnSerialization(
+                $reflectionClass,
+                $message,
+                $code,
+                $file,
+                $line
+            );
+        });
+
+        $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
+
+        restore_error_handler();
+
+        if ($error) {
+            throw $error;
+        }
+    }
+
+    /**
+     * @param ReflectionClass $reflectionClass
+     * @param string          $serializedString
+     *
+     * @throws UnexpectedValueException
+     *
+     * @return void
+     */
+    private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
+    {
+        try {
+            unserialize($serializedString);
+        } catch (Exception $exception) {
+            restore_error_handler();
+
+            throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
+        }
+    }
+
+    /**
+     * @param ReflectionClass $reflectionClass
+     *
+     * @return bool
+     */
+    private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
+    {
+        if (\PHP_VERSION_ID >= 50600) {
+            return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
+        }
+
+        return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
+    }
+
+    /**
+     * Verifies whether the given class is to be considered internal
+     *
+     * @param ReflectionClass $reflectionClass
+     *
+     * @return bool
+     */
+    private function hasInternalAncestors(ReflectionClass $reflectionClass)
+    {
+        do {
+            if ($reflectionClass->isInternal()) {
+                return true;
+            }
+        } while ($reflectionClass = $reflectionClass->getParentClass());
+
+        return false;
+    }
+
+    /**
+     * Verifies if the given PHP version implements the `Serializable` interface serialization
+     * with an incompatible serialization format. If that's the case, use serialization marker
+     * "C" instead of "O".
+     *
+     * @link http://news.php.net/php.internals/74654
+     *
+     * @param ReflectionClass $reflectionClass
+     *
+     * @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
+     *                or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
+     */
+    private function getSerializationFormat(ReflectionClass $reflectionClass)
+    {
+        if ($this->isPhpVersionWithBrokenSerializationFormat()
+            && $reflectionClass->implementsInterface('Serializable')
+        ) {
+            return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
+        }
+
+        return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
+    }
+
+    /**
+     * Checks whether the current PHP runtime uses an incompatible serialization format
+     *
+     * @return bool
+     */
+    private function isPhpVersionWithBrokenSerializationFormat()
+    {
+        return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
+    }
+
+    /**
+     * Checks if a class is cloneable
+     *
+     * @param ReflectionClass $reflection
+     *
+     * @return bool
+     */
+    private function isSafeToClone(ReflectionClass $reflection)
+    {
+        if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
+            return false;
+        }
+
+        // not cloneable if it implements `__clone`, as we want to avoid calling it
+        return ! $reflection->hasMethod('__clone');
+    }
+}