diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 0000000..eb439d1 --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,17 @@ +{ + "symbol-whitelist": [ + "ProxyManager\\Factory\\LazyLoadingValueHolderFactory", + "ProxyManager\\Proxy\\VirtualProxyInterface" + ], + "php-core-extensions": [ + "Core", + "date", + "json", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard" + ], + "scan-files": [] +} diff --git a/composer.json b/composer.json index 7626b5e..80b15a8 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ }, "require-dev": { "maglnet/composer-require-checker": "^4.2", + "friendsofphp/proxy-manager-lts": "^1.0", "phpunit/phpunit": "^9.5", "rector/rector": "^1.0.0", "roave/infection-static-analysis-plugin": "^1.18", @@ -38,6 +39,9 @@ "vimeo/psalm": "^4.30|^5.21", "yiisoft/test-support": "^1.4" }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Allows use lazy definitions" + }, "autoload": { "psr-4": { "Yiisoft\\Definitions\\": "src" diff --git a/src/ArrayDefinition.php b/src/ArrayDefinition.php index 20ed981..c4b7903 100644 --- a/src/ArrayDefinition.php +++ b/src/ArrayDefinition.php @@ -60,7 +60,7 @@ public function withReferenceContainer(?ContainerInterface $referenceContainer): } /** - * Create ArrayDefinition from array config. + * Create `ArrayDefinition` from array config. * * @psalm-param ArrayDefinitionConfig $config */ @@ -69,7 +69,7 @@ public static function fromConfig(array $config): self return new self( $config[self::CLASS_NAME], $config[self::CONSTRUCTOR] ?? [], - self::getMethodsAndPropertiesFromConfig($config) + self::getMethodsAndPropertiesFromConfig($config), ); } diff --git a/src/CallableDefinition.php b/src/CallableDefinition.php index 6de1b00..597f2e2 100644 --- a/src/CallableDefinition.php +++ b/src/CallableDefinition.php @@ -25,6 +25,7 @@ final class CallableDefinition implements DefinitionInterface { /** * @var array|callable + * * @psalm-var callable|array{0:class-string,1:string} */ private $callable; diff --git a/src/Helpers/DefinitionExtractor.php b/src/Helpers/DefinitionExtractor.php index c0f68fd..052ae6f 100644 --- a/src/Helpers/DefinitionExtractor.php +++ b/src/Helpers/DefinitionExtractor.php @@ -32,6 +32,7 @@ final class DefinitionExtractor * @throws NotInstantiableException * * @return ParameterDefinition[] + * * @psalm-return array */ public static function fromClassName(string $class): array @@ -61,6 +62,7 @@ public static function fromClassName(string $class): array * Extract dependency definitions from type hints of a function. * * @return ParameterDefinition[] + * * @psalm-return array */ public static function fromFunction(ReflectionFunctionAbstract $reflectionFunction): array diff --git a/src/Helpers/Normalizer.php b/src/Helpers/Normalizer.php index 6747deb..7d452a2 100644 --- a/src/Helpers/Normalizer.php +++ b/src/Helpers/Normalizer.php @@ -12,7 +12,6 @@ use Yiisoft\Definitions\Reference; use Yiisoft\Definitions\ValueDefinition; -use function array_key_exists; use function is_array; use function is_callable; use function is_object; @@ -49,7 +48,7 @@ final class Normalizer public static function normalize(mixed $definition, ?string $class = null): DefinitionInterface { // Reference - if ($definition instanceof ReferenceInterface) { + if ($definition instanceof DefinitionInterface) { return $definition; } @@ -75,7 +74,7 @@ public static function normalize(mixed $definition, ?string $class = null): Defi // Array definition if (is_array($definition)) { $config = $definition; - if (!array_key_exists(ArrayDefinition::CLASS_NAME, $config)) { + if (!isset($config[ArrayDefinition::CLASS_NAME])) { if ($class === null) { throw new InvalidConfigException( 'Array definition should contain the key "class": ' . var_export($definition, true) @@ -88,7 +87,7 @@ public static function normalize(mixed $definition, ?string $class = null): Defi } // Ready object - if (is_object($definition) && !($definition instanceof DefinitionInterface)) { + if (is_object($definition)) { return new ValueDefinition($definition); } diff --git a/src/LazyDefinition.php b/src/LazyDefinition.php new file mode 100644 index 0000000..4830728 --- /dev/null +++ b/src/LazyDefinition.php @@ -0,0 +1,39 @@ +get(LazyLoadingValueHolderFactory::class); + $definition = $this->definition; + + return $factory->createProxy( + $this->class, + static function (mixed &$wrappedObject) use ($container, $definition) { + $definition = Normalizer::normalize($definition); + $wrappedObject = $definition->resolve($container); + return true; + } + ); + } +} diff --git a/src/ReferencesArray.php b/src/ReferencesArray.php index ccf873e..d098dfa 100644 --- a/src/ReferencesArray.php +++ b/src/ReferencesArray.php @@ -67,6 +67,7 @@ final class ReferencesArray * @throws InvalidConfigException * * @return Reference[] + * * @psalm-suppress DocblockTypeContradiction */ public static function from(array $ids): array diff --git a/tests/Support/NotFinalClass.php b/tests/Support/NotFinalClass.php new file mode 100644 index 0000000..25f7512 --- /dev/null +++ b/tests/Support/NotFinalClass.php @@ -0,0 +1,20 @@ +arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } +} diff --git a/tests/Unit/ArrayDefinitionTest.php b/tests/Unit/ArrayDefinitionTest.php index 315d912..030e2a4 100644 --- a/tests/Unit/ArrayDefinitionTest.php +++ b/tests/Unit/ArrayDefinitionTest.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use TypeError; use Yiisoft\Definitions\ArrayDefinition; use Yiisoft\Definitions\Exception\InvalidConfigException; use Yiisoft\Definitions\Reference; @@ -524,6 +525,21 @@ public function testMagicMethods(): void ); } + public function testNonArrayMethodArguments(): void + { + $definition = ArrayDefinition::fromConfig([ + ArrayDefinition::CLASS_NAME => Mouse::class, + 'setNameAndEngine()' => 'kitty', + ]); + $container = new SimpleContainer(); + + $this->expectException(TypeError::class); + $this->expectExceptionMessage( + 'Yiisoft\Definitions\ArrayDefinition::resolveFunctionArguments(): Argument #3 ($arguments) must be of type array, string given' + ); + $definition->resolve($container); + } + public function testMultipleCall() { $definition = ArrayDefinition::fromConfig([ diff --git a/tests/Unit/Helpers/DefinitionResolverTest.php b/tests/Unit/Helpers/DefinitionResolverTest.php index c787543..6a611eb 100644 --- a/tests/Unit/Helpers/DefinitionResolverTest.php +++ b/tests/Unit/Helpers/DefinitionResolverTest.php @@ -43,10 +43,9 @@ public function testEnsureResolvableScalar(): void public function testEnsureResolvableDefinition(): void { $this->expectException(InvalidConfigException::class); - $this->expectExceptionMessageMatches( - '/^Only references are allowed in constructor arguments, a definition object was provided: (\\\\|)' . - preg_quote(ValueDefinition::class) . - '.*/' + $this->expectExceptionMessage( + 'Only references are allowed in constructor arguments, a definition object was provided: ' . + var_export(new ValueDefinition(7), true) ); DefinitionResolver::ensureResolvable(new ValueDefinition(7)); } diff --git a/tests/Unit/Helpers/DefinitionValidatorTest.php b/tests/Unit/Helpers/DefinitionValidatorTest.php index 29b4931..7673af7 100644 --- a/tests/Unit/Helpers/DefinitionValidatorTest.php +++ b/tests/Unit/Helpers/DefinitionValidatorTest.php @@ -342,10 +342,9 @@ public function testInteger(): void public function testDefinitionInArguments(): void { $this->expectException(InvalidConfigException::class); - $this->expectExceptionMessageMatches( - '/^Only references are allowed in constructor arguments, a definition object was provided: (\\\\|)' . - preg_quote(ValueDefinition::class) . - '.*/' + $this->expectExceptionMessage( + 'Only references are allowed in constructor arguments, a definition object was provided: ' . + var_export(new ValueDefinition(56), true) ); DefinitionValidator::validate([ 'class' => GearBox::class, diff --git a/tests/Unit/Helpers/NormalizerTest.php b/tests/Unit/Helpers/NormalizerTest.php index 8bd31ce..ccda5ac 100644 --- a/tests/Unit/Helpers/NormalizerTest.php +++ b/tests/Unit/Helpers/NormalizerTest.php @@ -51,6 +51,23 @@ public function testArray(): void $this->assertSame([], $definition->getMethodsAndProperties()); } + public function testNullClass(): void + { + /** @var ArrayDefinition $definition */ + $definition = Normalizer::normalize( + [ + 'class' => null, + '__construct()' => [42], + ], + GearBox::class + ); + + $this->assertInstanceOf(ArrayDefinition::class, $definition); + $this->assertSame(GearBox::class, $definition->getClass()); + $this->assertSame([42], $definition->getConstructorArguments()); + $this->assertSame([], $definition->getMethodsAndProperties()); + } + public function testReadyObject(): void { $container = new SimpleContainer(); diff --git a/tests/Unit/LazyDefinitionTest.php b/tests/Unit/LazyDefinitionTest.php new file mode 100644 index 0000000..cfd85f6 --- /dev/null +++ b/tests/Unit/LazyDefinitionTest.php @@ -0,0 +1,65 @@ + new LazyLoadingValueHolderFactory(), + ]); + + $class = Phone::class; + + $definition = new LazyDefinition([ArrayDefinition::CLASS_NAME => $class], $class); + + $this->expectException(InvalidProxiedClassException::class); + $definition->resolve($container); + } + + public function testNotFinalClass(): void + { + $container = new SimpleContainer([ + LazyLoadingValueHolderFactory::class => new LazyLoadingValueHolderFactory(), + ]); + + $class = NotFinalClass::class; + + $definition = new LazyDefinition([ArrayDefinition::CLASS_NAME => $class], $class); + + $object = $definition->resolve($container); + + self::assertInstanceOf($class, $object); + self::assertInstanceOf(LazyLoadingInterface::class, $object); + } + + public function testInterface(): void + { + $container = new SimpleContainer([ + LazyLoadingValueHolderFactory::class => new LazyLoadingValueHolderFactory(), + ]); + + $class = EngineInterface::class; + + $definition = new LazyDefinition([ArrayDefinition::CLASS_NAME => $class], $class); + + $engine = $definition->resolve($container); + + self::assertInstanceOf($class, $engine); + self::assertInstanceOf(LazyLoadingInterface::class, $engine); + } +}