├── LICENSE ├── README.md ├── composer.json └── src └── Doctrine └── Instantiator ├── Exception ├── ExceptionInterface.php ├── InvalidArgumentException.php └── UnexpectedValueException.php ├── Instantiator.php └── InstantiatorInterface.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Doctrine Project 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Doctrine Instantiator 2 | 3 | This library provides a way of avoiding usage of constructors when instantiating PHP classes. 4 | 5 | [![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator) 6 | [![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master) 7 | [![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator) 8 | 9 | [![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator) 10 | [![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator) 11 | 12 | ## Installation 13 | 14 | The suggested installation method is via [composer](https://getcomposer.org/): 15 | 16 | ```sh 17 | composer require doctrine/instantiator 18 | ``` 19 | 20 | ## Usage 21 | 22 | The instantiator is able to create new instances of any class without using the constructor or any API of the class 23 | itself: 24 | 25 | ```php 26 | $instantiator = new \Doctrine\Instantiator\Instantiator(); 27 | 28 | $instance = $instantiator->instantiate(\My\ClassName\Here::class); 29 | ``` 30 | 31 | ## Contributing 32 | 33 | Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! 34 | 35 | ## Credits 36 | 37 | This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which 38 | has been donated to the doctrine organization, and which is now deprecated in favour of this package. 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doctrine/instantiator", 3 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 4 | "type": "library", 5 | "license": "MIT", 6 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 7 | "keywords": [ 8 | "instantiate", 9 | "constructor" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Marco Pivetta", 14 | "email": "ocramius@gmail.com", 15 | "homepage": "https://ocramius.github.io/" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1" 20 | }, 21 | "require-dev": { 22 | "ext-phar": "*", 23 | "ext-pdo": "*", 24 | "doctrine/coding-standard": "^12", 25 | "phpbench/phpbench": "^1.2", 26 | "phpstan/phpstan": "^1.9.4", 27 | "phpstan/phpstan-phpunit": "^1.3", 28 | "phpunit/phpunit": "^10.5" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-0": { 37 | "DoctrineTest\\InstantiatorPerformance\\": "tests", 38 | "DoctrineTest\\InstantiatorTest\\": "tests", 39 | "DoctrineTest\\InstantiatorTestAsset\\": "tests" 40 | } 41 | }, 42 | "config": { 43 | "allow-plugins": { 44 | "dealerdirect/phpcodesniffer-composer-installer": true 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Doctrine/Instantiator/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | $reflectionClass 34 | * 35 | * @template T of object 36 | */ 37 | public static function fromAbstractClass(ReflectionClass $reflectionClass): self 38 | { 39 | return new self(sprintf( 40 | 'The provided class "%s" is abstract, and cannot be instantiated', 41 | $reflectionClass->getName(), 42 | )); 43 | } 44 | 45 | public static function fromEnum(string $className): self 46 | { 47 | return new self(sprintf( 48 | 'The provided class "%s" is an enum, and cannot be instantiated', 49 | $className, 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Doctrine/Instantiator/Exception/UnexpectedValueException.php: -------------------------------------------------------------------------------- 1 | $reflectionClass 20 | * 21 | * @template T of object 22 | */ 23 | public static function fromSerializationTriggeredException( 24 | ReflectionClass $reflectionClass, 25 | Exception $exception, 26 | ): self { 27 | return new self( 28 | sprintf( 29 | 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', 30 | $reflectionClass->getName(), 31 | ), 32 | 0, 33 | $exception, 34 | ); 35 | } 36 | 37 | /** 38 | * @phpstan-param ReflectionClass $reflectionClass 39 | * 40 | * @template T of object 41 | */ 42 | public static function fromUncleanUnSerialization( 43 | ReflectionClass $reflectionClass, 44 | string $errorString, 45 | int $errorCode, 46 | string $errorFile, 47 | int $errorLine, 48 | ): self { 49 | return new self( 50 | sprintf( 51 | 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' 52 | . 'in file "%s" at line "%d"', 53 | $reflectionClass->getName(), 54 | $errorFile, 55 | $errorLine, 56 | ), 57 | 0, 58 | new Exception($errorString, $errorCode), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Doctrine/Instantiator/Instantiator.php: -------------------------------------------------------------------------------- 1 | $className 51 | * 52 | * @phpstan-return T 53 | * 54 | * @throws ExceptionInterface 55 | * 56 | * @template T of object 57 | */ 58 | public function instantiate(string $className): object 59 | { 60 | if (isset(self::$cachedCloneables[$className])) { 61 | /** @phpstan-var T */ 62 | $cachedCloneable = self::$cachedCloneables[$className]; 63 | 64 | return clone $cachedCloneable; 65 | } 66 | 67 | if (isset(self::$cachedInstantiators[$className])) { 68 | $factory = self::$cachedInstantiators[$className]; 69 | 70 | return $factory(); 71 | } 72 | 73 | return $this->buildAndCacheFromFactory($className); 74 | } 75 | 76 | /** 77 | * Builds the requested object and caches it in static properties for performance 78 | * 79 | * @phpstan-param class-string $className 80 | * 81 | * @phpstan-return T 82 | * 83 | * @template T of object 84 | */ 85 | private function buildAndCacheFromFactory(string $className): object 86 | { 87 | $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); 88 | $instance = $factory(); 89 | 90 | if ($this->isSafeToClone(new ReflectionClass($instance))) { 91 | self::$cachedCloneables[$className] = clone $instance; 92 | } 93 | 94 | return $instance; 95 | } 96 | 97 | /** 98 | * Builds a callable capable of instantiating the given $className without 99 | * invoking its constructor. 100 | * 101 | * @phpstan-param class-string $className 102 | * 103 | * @phpstan-return callable(): T 104 | * 105 | * @throws InvalidArgumentException 106 | * @throws UnexpectedValueException 107 | * @throws ReflectionException 108 | * 109 | * @template T of object 110 | */ 111 | private function buildFactory(string $className): callable 112 | { 113 | $reflectionClass = $this->getReflectionClass($className); 114 | 115 | if ($this->isInstantiableViaReflection($reflectionClass)) { 116 | return [$reflectionClass, 'newInstanceWithoutConstructor']; 117 | } 118 | 119 | $serializedString = sprintf( 120 | '%s:%d:"%s":0:{}', 121 | is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, 122 | strlen($className), 123 | $className, 124 | ); 125 | 126 | $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); 127 | 128 | return static fn () => unserialize($serializedString); 129 | } 130 | 131 | /** 132 | * @phpstan-param class-string $className 133 | * 134 | * @phpstan-return ReflectionClass 135 | * 136 | * @throws InvalidArgumentException 137 | * @throws ReflectionException 138 | * 139 | * @template T of object 140 | */ 141 | private function getReflectionClass(string $className): ReflectionClass 142 | { 143 | if (! class_exists($className)) { 144 | throw InvalidArgumentException::fromNonExistingClass($className); 145 | } 146 | 147 | if (enum_exists($className, false)) { 148 | throw InvalidArgumentException::fromEnum($className); 149 | } 150 | 151 | $reflection = new ReflectionClass($className); 152 | 153 | if ($reflection->isAbstract()) { 154 | throw InvalidArgumentException::fromAbstractClass($reflection); 155 | } 156 | 157 | return $reflection; 158 | } 159 | 160 | /** 161 | * @phpstan-param ReflectionClass $reflectionClass 162 | * 163 | * @throws UnexpectedValueException 164 | * 165 | * @template T of object 166 | */ 167 | private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void 168 | { 169 | set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { 170 | $error = UnexpectedValueException::fromUncleanUnSerialization( 171 | $reflectionClass, 172 | $message, 173 | $code, 174 | $file, 175 | $line, 176 | ); 177 | 178 | return true; 179 | }); 180 | 181 | try { 182 | $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); 183 | } finally { 184 | restore_error_handler(); 185 | } 186 | 187 | if ($error) { 188 | throw $error; 189 | } 190 | } 191 | 192 | /** 193 | * @phpstan-param ReflectionClass $reflectionClass 194 | * 195 | * @throws UnexpectedValueException 196 | * 197 | * @template T of object 198 | */ 199 | private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void 200 | { 201 | try { 202 | unserialize($serializedString); 203 | } catch (Exception $exception) { 204 | throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); 205 | } 206 | } 207 | 208 | /** 209 | * @phpstan-param ReflectionClass $reflectionClass 210 | * 211 | * @template T of object 212 | */ 213 | private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool 214 | { 215 | return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); 216 | } 217 | 218 | /** 219 | * Verifies whether the given class is to be considered internal 220 | * 221 | * @phpstan-param ReflectionClass $reflectionClass 222 | * 223 | * @template T of object 224 | */ 225 | private function hasInternalAncestors(ReflectionClass $reflectionClass): bool 226 | { 227 | do { 228 | if ($reflectionClass->isInternal()) { 229 | return true; 230 | } 231 | 232 | $reflectionClass = $reflectionClass->getParentClass(); 233 | } while ($reflectionClass); 234 | 235 | return false; 236 | } 237 | 238 | /** 239 | * Checks if a class is cloneable 240 | * 241 | * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. 242 | * 243 | * @phpstan-param ReflectionClass $reflectionClass 244 | * 245 | * @template T of object 246 | */ 247 | private function isSafeToClone(ReflectionClass $reflectionClass): bool 248 | { 249 | return $reflectionClass->isCloneable() 250 | && ! $reflectionClass->hasMethod('__clone') 251 | && ! $reflectionClass->isSubclassOf(ArrayIterator::class); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/Doctrine/Instantiator/InstantiatorInterface.php: -------------------------------------------------------------------------------- 1 | $className 16 | * 17 | * @phpstan-return T 18 | * 19 | * @throws ExceptionInterface 20 | * 21 | * @template T of object 22 | */ 23 | public function instantiate(string $className): object; 24 | } 25 | --------------------------------------------------------------------------------