You are here

final class Instantiator in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php \Doctrine\Instantiator\Instantiator

@author Marco Pivetta <ocramius@gmail.com>

Hierarchy

Expanded class hierarchy of Instantiator

5 files declare their use of Instantiator
Doubler.php in vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php
Generator.php in vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php
InstantiatorPerformanceEvent.php in vendor/doctrine/instantiator/tests/DoctrineTest/InstantiatorPerformance/InstantiatorPerformanceEvent.php
InstantiatorTest.php in vendor/doctrine/instantiator/tests/DoctrineTest/InstantiatorTest/InstantiatorTest.php
ThrowPromise.php in vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php

File

vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php, line 33

Namespace

Doctrine\Instantiator
View source
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');
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Instantiator::$cachedCloneables private static property
Instantiator::$cachedInstantiators private static property @var \Closure[] of {
Instantiator::attemptInstantiationViaUnSerialization private function
Instantiator::buildAndCacheFromFactory private function Builds the requested object and caches it in static properties for performance
Instantiator::buildFactory private function Builds a {invoking its constructor.
Instantiator::checkIfUnSerializationIsSupported private function
Instantiator::getReflectionClass private function
Instantiator::getSerializationFormat private function 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".
Instantiator::hasInternalAncestors private function Verifies whether the given class is to be considered internal
Instantiator::instantiate public function Overrides InstantiatorInterface::instantiate
Instantiator::isInstantiableViaReflection private function
Instantiator::isPhpVersionWithBrokenSerializationFormat private function Checks whether the current PHP runtime uses an incompatible serialization format
Instantiator::isSafeToClone private function Checks if a class is cloneable
Instantiator::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER constant
Instantiator::SERIALIZATION_FORMAT_USE_UNSERIALIZER constant Markers used internally by PHP to define whether {the method {the {