You are here

Instantiator.php in Zircon Profile 8

File

vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php
View source
<?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');
  }

}

Classes

Namesort descending Description
Instantiator @author Marco Pivetta <ocramius@gmail.com>