buildAndCacheFromFactory($className); } /** * Builds the requested object and caches it in static properties for performance * * @return object */ private function buildAndCacheFromFactory(string $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 callable capable of instantiating the given $className without * invoking its constructor. * * @throws InvalidArgumentException * @throws UnexpectedValueException * @throws ReflectionException */ private function buildFactory(string $className) : callable { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return [$reflectionClass, 'newInstanceWithoutConstructor']; } $serializedString = sprintf( '%s:%d:"%s":0:{}', self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return static function () use ($serializedString) { return unserialize($serializedString); }; } /** * @param string $className * * @throws InvalidArgumentException * @throws ReflectionException */ private function getReflectionClass($className) : ReflectionClass { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } /** * @throws UnexpectedValueException */ private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString) : void { set_error_handler(static function ($code, $message, $file, $line) use ($reflectionClass, & $error) : void { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line ); }); try { $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); } finally { restore_error_handler(); } if ($error) { throw $error; } } /** * @throws UnexpectedValueException */ private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString) : void { try { unserialize($serializedString); } catch (Exception $exception) { throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } private function isInstantiableViaReflection(ReflectionClass $reflectionClass) : bool { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } /** * Verifies whether the given class is to be considered internal */ private function hasInternalAncestors(ReflectionClass $reflectionClass) : bool { do { if ($reflectionClass->isInternal()) { return true; } $reflectionClass = $reflectionClass->getParentClass(); } while ($reflectionClass); return false; } /** * Checks if a class is cloneable * * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. */ private function isSafeToClone(ReflectionClass $reflection) : bool { return $reflection->isCloneable() && ! $reflection->hasMethod('__clone'); } }