Repository: doctrine/instantiator Branch: 2.1.x Commit: eede1167376e Files: 46 Total size: 44.1 KB Directory structure: gitextract_gga815b3/ ├── .doctrine-project.json ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── dependabot.yml │ └── workflows/ │ ├── coding-standards.yml │ ├── continuous-integration.yml │ ├── phpbench.yml │ ├── release-on-milestone-closed.yml │ ├── static-analysis.yml │ └── website-schema.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── docs/ │ └── en/ │ ├── index.rst │ └── sidebar.rst ├── phpbench.json ├── phpcs.xml.dist ├── phpmd.xml.dist ├── phpstan.neon.dist ├── phpunit.xml.dist ├── src/ │ ├── Exception/ │ │ ├── ExceptionInterface.php │ │ ├── InvalidArgumentException.php │ │ └── UnexpectedValueException.php │ ├── Instantiator.php │ └── InstantiatorInterface.php └── tests/ ├── InstantiatorPerformance/ │ └── InstantiatorPerformanceBench.php ├── InstantiatorTest/ │ ├── Exception/ │ │ ├── InvalidArgumentExceptionTest.php │ │ └── UnexpectedValueExceptionTest.php │ └── InstantiatorTest.php └── InstantiatorTestAsset/ ├── AbstractClassAsset.php ├── ArrayObjectAsset.php ├── ExceptionAsset.php ├── FinalExceptionAsset.php ├── PharAsset.php ├── PharExceptionAsset.php ├── SerializableArrayObjectAsset.php ├── SerializableFinalInternalChildAsset.php ├── SimpleEnumAsset.php ├── SimpleSerializableAsset.php ├── SimpleTraitAsset.php ├── UnCloneableAsset.php ├── UnserializeExceptionArrayObjectAsset.php ├── WakeUpNoticesAsset.php └── XMLReaderAsset.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .doctrine-project.json ================================================ { "active": true, "name": "Instantiator", "slug": "instantiator", "docsSlug": "doctrine-instantiator", "versions": [ { "name": "2.2", "branchName": "2.2.x", "slug": "2.2", "upcoming": true }, { "name": "2.1", "branchName": "2.1.x", "slug": "2.1", "current": true }, { "name": "2.0", "slug": "2.0", "maintained": false }, { "name": "1.5", "slug": "1.5", "maintained": false }, { "name": "1.4", "slug": "1.4", "maintained": false }, { "name": "1.3", "slug": "1.3", "maintained": false } ] } ================================================ FILE: .gitattributes ================================================ /.github export-ignore /tests export-ignore /docs export-ignore .doctrine-project.json export-ignore .gitattributes export-ignore .gitignore export-ignore phpbench.json export-ignore phpcs.xml.dist export-ignore phpmd.xml.dist export-ignore phpstan.neon.dist export-ignore phpunit.xml.dist export-ignore composer.lock export-ignore /CONTRIBUTING.md export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ patreon: phpdoctrine tidelift: packagist/doctrine%2Finstantiator custom: https://www.doctrine-project.org/sponsorship.html ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "CI" ================================================ FILE: .github/workflows/coding-standards.yml ================================================ name: "Coding Standards" on: pull_request: branches: - "*.x" push: branches: - "*.x" jobs: coding-standards: name: "Coding Standards" uses: "doctrine/.github/.github/workflows/coding-standards.yml@14.0.0" ================================================ FILE: .github/workflows/continuous-integration.yml ================================================ name: "Continuous Integration" on: pull_request: branches: - "*.x" push: branches: - "*.x" jobs: phpunit: name: "PHPUnit" uses: "doctrine/.github/.github/workflows/continuous-integration.yml@14.0.0" with: php-versions: '["8.4", "8.5"]' secrets: CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" ================================================ FILE: .github/workflows/phpbench.yml ================================================ name: "Performance benchmark" on: pull_request: branches: - "*.x" push: branches: - "*.x" env: fail-fast: true jobs: phpbench: name: "PHPBench" runs-on: "ubuntu-22.04" strategy: matrix: php-version: - "8.4" - "8.5" steps: - name: "Checkout" uses: "actions/checkout@v6" with: fetch-depth: 2 - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: php-version: "${{ matrix.php-version }}" coverage: "pcov" ini-values: "zend.assertions=1" - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v4" - name: "Run PHPBench" run: "php ./vendor/bin/phpbench run --iterations=3 --warmup=1 --report=aggregate" ================================================ FILE: .github/workflows/release-on-milestone-closed.yml ================================================ name: "Automatic Releases" on: milestone: types: - "closed" jobs: release: name: "Git tag, release & create merge-up PR" uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.1.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }} ================================================ FILE: .github/workflows/static-analysis.yml ================================================ name: "Static Analysis" on: pull_request: branches: - "*.x" push: branches: - "*.x" jobs: static-analysis: name: "Static Analysis" uses: "doctrine/.github/.github/workflows/phpstan.yml@14.0.0" ================================================ FILE: .github/workflows/website-schema.yml ================================================ name: "Website config validation" on: pull_request: branches: - "*.x" paths: - ".doctrine-project.json" - ".github/workflows/website-schema.yml" push: branches: - "*.x" paths: - ".doctrine-project.json" - ".github/workflows/website-schema.yml" jobs: json-validate: name: "Validate JSON schema" uses: "doctrine/.github/.github/workflows/website-schema.yml@14.0.0" ================================================ FILE: .gitignore ================================================ phpunit.xml /.phpunit.cache build vendor /composer.lock coverage.clover /phpcs.xml /.phpcs-cache /phpstan.neon ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing * Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard) * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) * Any contribution must provide tests for additional introduced conditions * Any un-confirmed issue needs a failing test case before being accepted * Pull requests must be sent from a new hotfix/feature branch, not from `master`. ## Installation To install the project and run the tests, you need to clone it first: ```sh $ git clone git://github.com/doctrine/instantiator.git ``` You will then need to run a composer installation: ```sh $ cd Instantiator $ curl -s https://getcomposer.org/installer | php $ php composer.phar update ``` ## Testing The PHPUnit version to be used is the one installed as a dev- dependency via composer: ```sh $ ./vendor/bin/phpunit ``` Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won't be merged. ================================================ FILE: LICENSE ================================================ Copyright (c) 2014 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Doctrine Instantiator This library provides a way of avoiding usage of constructors when instantiating PHP classes. [![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator) [![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master) [![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator) [![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator) [![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator) ## Installation The suggested installation method is via [composer](https://getcomposer.org/): ```sh composer require doctrine/instantiator ``` ## Usage The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: ```php $instantiator = new \Doctrine\Instantiator\Instantiator(); $instance = $instantiator->instantiate(\My\ClassName\Here::class); ``` ## Contributing Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! ## Credits This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which has been donated to the doctrine organization, and which is now deprecated in favour of this package. ================================================ FILE: composer.json ================================================ { "name": "doctrine/instantiator", "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "type": "library", "license": "MIT", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "instantiate", "constructor" ], "authors": [ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", "homepage": "https://ocramius.github.io/" } ], "require": { "php": "^8.4" }, "require-dev": { "ext-phar": "*", "ext-pdo": "*", "doctrine/coding-standard": "^14", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^2.1", "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^10.5.58" }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/" } }, "autoload-dev": { "psr-4": { "DoctrineTest\\InstantiatorPerformance\\": "tests/InstantiatorPerformance", "DoctrineTest\\InstantiatorTest\\": "tests/InstantiatorTest", "DoctrineTest\\InstantiatorTestAsset\\": "tests/InstantiatorTestAsset" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } ================================================ FILE: docs/en/index.rst ================================================ Introduction ============ This library provides a way of avoiding usage of constructors when instantiating PHP classes. Installation ============ The suggested installation method is via `composer`_: .. code-block:: console $ composer require doctrine/instantiator Usage ===== The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: .. code-block:: php instantiate(User::class); Contributing ============ - Follow the `Doctrine Coding Standard`_ - The project will follow strict `object calisthenics`_ - Any contribution must provide tests for additional introduced conditions - Any un-confirmed issue needs a failing test case before being accepted - Pull requests must be sent from a new hotfix/feature branch, not from ``master``. Testing ======= The PHPUnit version to be used is the one installed as a dev- dependency via composer: .. code-block:: console $ ./vendor/bin/phpunit Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won’t be merged. Credits ======= This library was migrated from `ocramius/instantiator`_, which has been donated to the doctrine organization, and which is now deprecated in favour of this package. .. _composer: https://getcomposer.org/ .. _CONTRIBUTING.md: CONTRIBUTING.md .. _ocramius/instantiator: https://github.com/Ocramius/Instantiator .. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard .. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php ================================================ FILE: docs/en/sidebar.rst ================================================ .. toctree:: :depth: 3 index ================================================ FILE: phpbench.json ================================================ { "runner.bootstrap": "vendor/autoload.php", "runner.path": "tests/InstantiatorPerformance" } ================================================ FILE: phpcs.xml.dist ================================================ src tests tests/InstantiatorTestAsset/AbstractClassAsset.php src/Exception/UnexpectedValueException.php src/Exception/InvalidArgumentException.php src/Exception/ExceptionInterface.php src/InstantiatorInterface.php ================================================ FILE: phpmd.xml.dist ================================================ ================================================ FILE: phpstan.neon.dist ================================================ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon parameters: level: max phpVersion: 80400 paths: - src - tests ignoreErrors: # PHPStan is unable to infer the return type of unserialize() in this case. - message: '#Method Doctrine\\Instantiator\\Instantiator\:\:buildFactory\(\) should return callable\(\): T but returns Closure\(\): mixed\.#' path: 'src/Instantiator.php' # dynamic properties confuse static analysis - message: '#Access to an undefined property object::\$foo\.#' path: 'tests/InstantiatorTest/InstantiatorTest.php' # The property is static, we cannot use templating here - message: '#instantiate\(\) should return#' path: 'src/Instantiator.php' # this trait is not meant to be use'd - message: '#is used zero times#' path: 'tests/InstantiatorTestAsset/SimpleTraitAsset.php' ================================================ FILE: phpunit.xml.dist ================================================ ./tests/InstantiatorTest ./src ================================================ FILE: src/Exception/ExceptionInterface.php ================================================ $reflectionClass * * @template T of object */ public static function fromAbstractClass(ReflectionClass $reflectionClass): self { return new self(sprintf( 'The provided class "%s" is abstract, and cannot be instantiated', $reflectionClass->getName(), )); } public static function fromEnum(string $className): self { return new self(sprintf( 'The provided class "%s" is an enum, and cannot be instantiated', $className, )); } } ================================================ FILE: src/Exception/UnexpectedValueException.php ================================================ $reflectionClass * * @template T of object */ public static function fromSerializationTriggeredException( ReflectionClass $reflectionClass, Exception $exception, ): self { return new self( sprintf( 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', $reflectionClass->getName(), ), 0, $exception, ); } /** * @phpstan-param ReflectionClass $reflectionClass * * @template T of object */ public static function fromUncleanUnSerialization( ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine, ): self { return new self( sprintf( 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine, ), 0, new Exception($errorString, $errorCode), ); } } ================================================ FILE: src/Instantiator.php ================================================ */ private static array $cachedInstantiators = []; /** * Array of objects that can directly be cloned, indexed by class name. * * @var object[] */ private static array $cachedCloneables = []; /** * @phpstan-param class-string $className * * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate(string $className): object { if (isset(self::$cachedCloneables[$className])) { /** @phpstan-var T */ $cachedCloneable = self::$cachedCloneables[$className]; return clone $cachedCloneable; } 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 * * @phpstan-param class-string $className * * @phpstan-return T * * @template T of object */ private function buildAndCacheFromFactory(string $className): object { $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. * * @phpstan-param class-string $className * * @phpstan-return callable(): T * * @throws InvalidArgumentException * @throws UnexpectedValueException * @throws ReflectionException * * @template T of object */ private function buildFactory(string $className): callable { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return [$reflectionClass, 'newInstanceWithoutConstructor']; } $serializedString = sprintf( '%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className, ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return static fn () => unserialize($serializedString); } /** * @phpstan-param class-string $className * * @phpstan-return ReflectionClass * * @throws InvalidArgumentException * @throws ReflectionException * * @template T of object */ private function getReflectionClass(string $className): ReflectionClass { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } if (enum_exists($className, false)) { throw InvalidArgumentException::fromEnum($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } /** * @phpstan-param ReflectionClass $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void { set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line, ); return true; }); try { $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); } finally { restore_error_handler(); } if ($error) { throw $error; } } /** * @phpstan-param ReflectionClass $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void { try { unserialize($serializedString); } catch (Exception $exception) { throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } /** * @phpstan-param ReflectionClass $reflectionClass * * @template T of object */ private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } /** * Verifies whether the given class is to be considered internal * * @phpstan-param ReflectionClass $reflectionClass * * @template T of object */ 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. * * @phpstan-param ReflectionClass $reflectionClass * * @template T of object */ private function isSafeToClone(ReflectionClass $reflectionClass): bool { return $reflectionClass->isCloneable() && ! $reflectionClass->hasMethod('__clone') && ! $reflectionClass->isSubclassOf(ArrayIterator::class); } } ================================================ FILE: src/InstantiatorInterface.php ================================================ $className * * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate(string $className): object; } ================================================ FILE: tests/InstantiatorPerformance/InstantiatorPerformanceBench.php ================================================ instantiator = new Instantiator(); $this->instantiator->instantiate(self::class); $this->instantiator->instantiate(ArrayObject::class); $this->instantiator->instantiate(SimpleSerializableAsset::class); $this->instantiator->instantiate(SerializableArrayObjectAsset::class); $this->instantiator->instantiate(UnCloneableAsset::class); } /** @Revs(20000) */ public function benchInstantiateSelf(): void { $this->instantiator->instantiate(self::class); } /** @Revs(20000) */ public function benchInstantiateInternalClass(): void { $this->instantiator->instantiate(ArrayObject::class); } /** @Revs(20000) */ public function benchInstantiateSimpleSerializableAssetClass(): void { $this->instantiator->instantiate(SimpleSerializableAsset::class); } /** @Revs(20000) */ public function benchInstantiateSerializableArrayObjectAsset(): void { $this->instantiator->instantiate(SerializableArrayObjectAsset::class); } /** @Revs(20000) */ public function benchInstantiateUnCloneableAsset(): void { $this->instantiator->instantiate(UnCloneableAsset::class); } } ================================================ FILE: tests/InstantiatorTest/Exception/InvalidArgumentExceptionTest.php ================================================ getMessage()); } public function testFromNonExistingTypeWithTrait(): void { $exception = InvalidArgumentException::fromNonExistingClass(SimpleTraitAsset::class); self::assertSame( sprintf('The provided type "%s" is a trait, and cannot be instantiated', SimpleTraitAsset::class), $exception->getMessage(), ); } public function testFromNonExistingTypeWithInterface(): void { $exception = InvalidArgumentException::fromNonExistingClass(InstantiatorInterface::class); self::assertSame( sprintf( 'The provided type "%s" is an interface, and cannot be instantiated', InstantiatorInterface::class, ), $exception->getMessage(), ); } public function testFromAbstractClass(): void { $reflection = new ReflectionClass(AbstractClassAsset::class); $exception = InvalidArgumentException::fromAbstractClass($reflection); self::assertSame( sprintf( 'The provided class "%s" is abstract, and cannot be instantiated', AbstractClassAsset::class, ), $exception->getMessage(), ); } } ================================================ FILE: tests/InstantiatorTest/Exception/UnexpectedValueExceptionTest.php ================================================ getPrevious()); self::assertSame( 'An exception was raised while trying to instantiate an instance of "' . self::class . '" via un-serialization', $exception->getMessage(), ); } public function testFromUncleanUnSerialization(): void { $reflection = new ReflectionClass(AbstractClassAsset::class); $exception = UnexpectedValueException::fromUncleanUnSerialization($reflection, 'foo', 123, 'bar', 456); self::assertSame( sprintf( 'Could not produce an instance of "%s" ' . 'via un-serialization, since an error was triggered in file "bar" at line "456"', AbstractClassAsset::class, ), $exception->getMessage(), ); $previous = $exception->getPrevious(); self::assertInstanceOf(Exception::class, $previous); self::assertSame('foo', $previous->getMessage()); self::assertSame(123, $previous->getCode()); } } ================================================ FILE: tests/InstantiatorTest/InstantiatorTest.php ================================================ instantiator = new Instantiator(); } /** * @phpstan-param class-string $className * * @dataProvider getInstantiableClasses */ public function testCanInstantiate(string $className): void { self::assertInstanceOf($className, $this->instantiator->instantiate($className)); } /** * @phpstan-param class-string $className * * @dataProvider getInstantiableClasses */ public function testInstantiatesSeparateInstances(string $className): void { $instance1 = $this->instantiator->instantiate($className); $instance2 = $this->instantiator->instantiate($className); self::assertEquals($instance1, $instance2); self::assertNotSame($instance1, $instance2); } public function testExceptionOnUnSerializationException(): void { $this->expectException(UnexpectedValueException::class); $this->instantiator->instantiate(PDORow::class); } /** * @phpstan-param class-string $invalidClassName * * @dataProvider getInvalidClassNames */ public function testInstantiationFromNonExistingClass(string $invalidClassName): void { $this->expectException(InvalidArgumentException::class); $this->instantiator->instantiate($invalidClassName); } public function testInstancesAreNotCloned(): void { $namespace = __NAMESPACE__; $className = 'TemporaryClass' . str_replace('.', '', uniqid('', true)); eval(<<< PHP namespace $namespace; #[\AllowDynamicProperties] class $className {} PHP ); /** @phpstan-var class-string */ $classNameWithNamespace = $namespace . '\\' . $className; $instance = $this->instantiator->instantiate($classNameWithNamespace); $instance->foo = 'bar'; $instance2 = $this->instantiator->instantiate($classNameWithNamespace); self::assertObjectNotHasProperty('foo', $instance2); } /** * Provides a list of instantiable classes (existing) * * @return string[][] * @phpstan-return list */ public static function getInstantiableClasses(): array { return [ [stdClass::class], [self::class], [Instantiator::class], [Exception::class], [PharException::class], [SimpleSerializableAsset::class], [ExceptionAsset::class], [FinalExceptionAsset::class], [PharExceptionAsset::class], [UnCloneableAsset::class], [XMLReaderAsset::class], [PharException::class], [ArrayObject::class], [ArrayObjectAsset::class], [SerializableArrayObjectAsset::class], [WakeUpNoticesAsset::class], [UnserializeExceptionArrayObjectAsset::class], [SerializableFinalInternalChildAsset::class], ]; } /** * Provides a list of instantiable classes (existing) * * @return Generator */ public static function getInvalidClassNames(): Generator { yield 'invalid string' => [self::class . str_replace('.', '', uniqid('', true))]; yield 'interface' => [InstantiatorInterface::class]; yield 'abstract class' => [AbstractClassAsset::class]; yield 'trait' => [SimpleTraitAsset::class]; yield 'enum' => [SimpleEnumAsset::class]; } } ================================================ FILE: tests/InstantiatorTestAsset/AbstractClassAsset.php ================================================ */ class ArrayObjectAsset extends ArrayObject { /** * Constructor - should not be called * * @throws BadMethodCallException */ public function __construct() { throw new BadMethodCallException('Not supposed to be called!'); } } ================================================ FILE: tests/InstantiatorTestAsset/ExceptionAsset.php ================================================ */ class SerializableArrayObjectAsset extends ArrayObject implements Serializable { /** * Constructor - should not be called * * @throws BadMethodCallException */ public function __construct() { throw new BadMethodCallException('Not supposed to be called!'); } /** * {@inheritDoc} * * Should not be called * * @throws BadMethodCallException */ public function unserialize($serialized): void { throw new BadMethodCallException('Not supposed to be called!'); } /** @param mixed[] $data */ public function __unserialize(array $data): void { throw new BadMethodCallException('Not supposed to be called!'); } } ================================================ FILE: tests/InstantiatorTestAsset/SerializableFinalInternalChildAsset.php ================================================ */ final class SerializableFinalInternalChildAsset extends ArrayIterator { } ================================================ FILE: tests/InstantiatorTestAsset/SimpleEnumAsset.php ================================================ */ class UnserializeExceptionArrayObjectAsset extends ArrayObject { /** * {@inheritDoc} */ public function __wakeup() { throw new BadMethodCallException(); } } ================================================ FILE: tests/InstantiatorTestAsset/WakeUpNoticesAsset.php ================================================ */ class WakeUpNoticesAsset extends ArrayObject { /** * Wakeup method called after un-serialization */ public function __wakeup(): void { trigger_error('Something went bananas while un-serializing this instance'); } } ================================================ FILE: tests/InstantiatorTestAsset/XMLReaderAsset.php ================================================