Viewing file: Instantiator.php (8.19 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?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'); } }
|