diff --git a/src/Definition/AttributeDefinition.php b/src/Definition/AttributeDefinition.php new file mode 100644 index 00000000..e8a1400e --- /dev/null +++ b/src/Definition/AttributeDefinition.php @@ -0,0 +1,33 @@ + */ + private array $arguments, + ) {} + + public function class(): ClassDefinition + { + return $this->class; + } + + /** + * @return list + */ + public function arguments(): array + { + return $this->arguments; + } + + public function instantiate(): object + { + return new ($this->class->type()->className())(...$this->arguments); + } +} diff --git a/src/Definition/Attributes.php b/src/Definition/Attributes.php index 3a08a2d0..0c1ff476 100644 --- a/src/Definition/Attributes.php +++ b/src/Definition/Attributes.php @@ -6,24 +6,76 @@ use Countable; use IteratorAggregate; +use Traversable; + +use function array_filter; +use function count; +use function is_a; /** * @internal * - * @extends IteratorAggregate + * @implements IteratorAggregate */ -interface Attributes extends IteratorAggregate, Countable +final class Attributes implements IteratorAggregate, Countable { + private static self $empty; + + /** @var list */ + private array $attributes; + + /** + * @no-named-arguments + */ + public function __construct(AttributeDefinition ...$attributes) + { + $this->attributes = $attributes; + } + + public static function empty(): self + { + return self::$empty ??= new self(); + } + + public function has(string $className): bool + { + foreach ($this->attributes as $attribute) { + if (is_a($attribute->class()->type()->className(), $className, true)) { + return true; + } + } + + return false; + } + + /** + * @param callable(AttributeDefinition): bool $callback + */ + public function filter(callable $callback): self + { + return new self( + ...array_filter($this->attributes, $callback) + ); + } + + public function count(): int + { + return count($this->attributes); + } + /** - * @param class-string $className + * @return list */ - public function has(string $className): bool; + public function toArray(): array + { + return $this->attributes; + } /** - * @template T of object - * - * @param class-string $className - * @return list + * @return Traversable */ - public function ofType(string $className): array; + public function getIterator(): Traversable + { + yield from $this->attributes; + } } diff --git a/src/Definition/AttributesContainer.php b/src/Definition/AttributesContainer.php deleted file mode 100644 index f60353c8..00000000 --- a/src/Definition/AttributesContainer.php +++ /dev/null @@ -1,80 +0,0 @@ - */ - private array $attributes; - - /** - * @no-named-arguments - * @param AttributeParam ...$attributes - */ - public function __construct(array ...$attributes) - { - $this->attributes = $attributes; - } - - public static function empty(): self - { - return self::$empty ??= new self(); - } - - public function has(string $className): bool - { - foreach ($this->attributes as $attribute) { - if (is_a($attribute['class'], $className, true)) { - return true; - } - } - - return false; - } - - public function ofType(string $className): array - { - $attributes = array_filter( - $this->attributes, - static fn (array $attribute): bool => is_a($attribute['class'], $className, true) - ); - - /** @phpstan-ignore-next-line */ - return array_values(array_map( - fn (array $attribute) => $attribute['callback'](), - $attributes - )); - } - - public function count(): int - { - return count($this->attributes); - } - - /** - * @return Traversable - */ - public function getIterator(): Traversable - { - foreach ($this->attributes as $attribute) { - yield $attribute['callback'](); - } - } -} diff --git a/src/Definition/NativeAttributes.php b/src/Definition/NativeAttributes.php deleted file mode 100644 index 064702ae..00000000 --- a/src/Definition/NativeAttributes.php +++ /dev/null @@ -1,89 +0,0 @@ -> */ - private array $definition = []; - - /** - * @param ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection - */ - public function __construct(ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection) - { - $attributes = array_filter( - array_map( - static function (ReflectionAttribute $attribute) { - try { - $instance = $attribute->newInstance(); - - return [ - 'class' => $attribute->getName(), - 'callback' => fn () => $instance - ]; - } catch (Error) { - // Race condition when the attribute is affected to a property/parameter - // that was PROMOTED, in this case the attribute will be applied to both - // ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute - // class is configured to support only ONE of them (parameter OR property) - // https://wiki.php.net/rfc/constructor_promotion#attributes for more details. - // Ignore attribute if the instantiation failed. - return null; - } - }, - $reflection->getAttributes(), - ), - ); - - foreach ($reflection->getAttributes() as $attribute) { - $this->definition[$attribute->getName()] = $attribute->getArguments(); - } - - $this->delegate = new AttributesContainer(...$attributes); - } - - public function has(string $className): bool - { - return $this->delegate->has($className); - } - - public function ofType(string $className): array - { - return $this->delegate->ofType($className); - } - - public function getIterator(): Traversable - { - yield from $this->delegate; - } - - public function count(): int - { - return count($this->delegate); - } - - /** - * @return array> - */ - public function definition(): array - { - return $this->definition; - } -} diff --git a/src/Definition/Repository/AttributesRepository.php b/src/Definition/Repository/AttributesRepository.php index 9eff034d..a5291373 100644 --- a/src/Definition/Repository/AttributesRepository.php +++ b/src/Definition/Repository/AttributesRepository.php @@ -4,18 +4,14 @@ namespace CuyZ\Valinor\Definition\Repository; -use CuyZ\Valinor\Definition\Attributes; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; -use ReflectionParameter; -use ReflectionProperty; +use CuyZ\Valinor\Definition\AttributeDefinition; +use ReflectionAttribute; /** @internal */ interface AttributesRepository { /** - * @param ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflector + * @param ReflectionAttribute $reflection */ - public function for(ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflector): Attributes; + public function for(ReflectionAttribute $reflection): AttributeDefinition; } diff --git a/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php b/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php index 88706307..8f09ba7a 100644 --- a/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php +++ b/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php @@ -5,8 +5,6 @@ namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler; use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Definition\AttributesContainer; -use CuyZ\Valinor\Definition\NativeAttributes; use function count; use function implode; @@ -17,29 +15,35 @@ /** @internal */ final class AttributesCompiler { + public function __construct(private ClassDefinitionCompiler $classDefinitionCompiler) {} + public function compile(Attributes $attributes): string { if (count($attributes) === 0) { - return AttributesContainer::class . '::empty()'; + return Attributes::class . '::empty()'; } - assert($attributes instanceof NativeAttributes); - - $attributesListCode = $this->compileNativeAttributes($attributes); + $attributesListCode = $this->compileAttributes($attributes); return <<definition() as $className => $arguments) { - $argumentsCode = $this->compileAttributeArguments($arguments); + foreach ($attributes as $attribute) { + $class = $this->classDefinitionCompiler->compile($attribute->class()); + $arguments = $this->compileAttributeArguments($attribute->arguments()); - $attributesListCode[] = "['class' => '$className', 'callback' => fn () => new $className($argumentsCode)]"; + $attributesListCode[] = <<typeCompiler = new TypeCompiler(); - $this->attributesCompiler = new AttributesCompiler(); + $this->attributesCompiler = new AttributesCompiler($this); $this->methodCompiler = new MethodDefinitionCompiler($this->typeCompiler, $this->attributesCompiler); $this->propertyCompiler = new PropertyDefinitionCompiler($this->typeCompiler, $this->attributesCompiler); diff --git a/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php b/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php index f885653e..d6db766f 100644 --- a/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php +++ b/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php @@ -22,9 +22,9 @@ final class FunctionDefinitionCompiler implements CacheCompiler public function __construct() { $this->typeCompiler = new TypeCompiler(); - $this->attributesCompiler = new AttributesCompiler(); + $this->attributesCompiler = new AttributesCompiler(new ClassDefinitionCompiler()); - $this->parameterCompiler = new ParameterDefinitionCompiler($this->typeCompiler, new AttributesCompiler()); + $this->parameterCompiler = new ParameterDefinitionCompiler($this->typeCompiler, $this->attributesCompiler); } public function compile(mixed $value): string diff --git a/src/Definition/Repository/Reflection/NativeAttributesRepository.php b/src/Definition/Repository/Reflection/NativeAttributesRepository.php deleted file mode 100644 index 9da982e2..00000000 --- a/src/Definition/Repository/Reflection/NativeAttributesRepository.php +++ /dev/null @@ -1,18 +0,0 @@ -classDefinitionRepository->for(NativeClassType::for($reflection->getName())); + + return new AttributeDefinition( + $class, + $reflection->getArguments(), + ); + } +} diff --git a/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php b/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php index 7824749b..39b61506 100644 --- a/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php +++ b/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php @@ -4,6 +4,8 @@ namespace CuyZ\Valinor\Definition\Repository\Reflection; +use CuyZ\Valinor\Definition\AttributeDefinition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\Exception\ClassTypeAliasesDuplication; use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClass; @@ -26,6 +28,7 @@ use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\UnresolvableType; use CuyZ\Valinor\Utility\Reflection\Reflection; +use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; @@ -40,7 +43,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi { private TypeParserFactory $typeParserFactory; - private AttributesRepository $attributesFactory; + private AttributesRepository $attributesRepository; private ReflectionPropertyDefinitionBuilder $propertyBuilder; @@ -49,12 +52,12 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi /** @var array */ private array $typeResolver = []; - public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesFactory) + public function __construct(TypeParserFactory $typeParserFactory) { $this->typeParserFactory = $typeParserFactory; - $this->attributesFactory = $attributesFactory; - $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($attributesFactory); - $this->methodBuilder = new ReflectionMethodDefinitionBuilder($attributesFactory); + $this->attributesRepository = new ReflectionAttributesRepository($this); + $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($this->attributesRepository); + $this->methodBuilder = new ReflectionMethodDefinitionBuilder($this->attributesRepository); } public function for(ClassType $type): ClassDefinition @@ -63,7 +66,7 @@ public function for(ClassType $type): ClassDefinition return new ClassDefinition( $type, - $this->attributesFactory->for($reflection), + new Attributes(...$this->attributes($reflection)), new Properties(...$this->properties($type)), new Methods(...$this->methods($type)), $reflection->isFinal(), @@ -71,6 +74,18 @@ public function for(ClassType $type): ClassDefinition ); } + /** + * @param ReflectionClass $reflection + * @return list + */ + private function attributes(ReflectionClass $reflection): array + { + return array_map( + fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute), + Reflection::attributes($reflection) + ); + } + /** * @return list */ @@ -82,7 +97,7 @@ function (ReflectionProperty $property) use ($type) { return $this->propertyBuilder->for($property, $typeResolver); }, - Reflection::class($type->className())->getProperties() + Reflection::class($type->className())->getProperties(), ); } @@ -147,11 +162,11 @@ private function typeResolver(ClassType $type, ReflectionClass $target): Reflect $advancedParser = $this->typeParserFactory->get( new ClassContextSpecification($type->className()), new AliasSpecification(Reflection::class($type->className())), - new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases) + new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases), ); $nativeParser = $this->typeParserFactory->get( - new ClassContextSpecification($type->className()) + new ClassContextSpecification($type->className()), ); return $this->typeResolver[$typeKey] = new ReflectionTypeResolver($nativeParser, $advancedParser); diff --git a/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php b/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php index 4dc72a31..05a86779 100644 --- a/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php +++ b/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php @@ -4,6 +4,8 @@ namespace CuyZ\Valinor\Definition\Repository\Reflection; +use CuyZ\Valinor\Definition\AttributeDefinition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Definition\Parameters; use CuyZ\Valinor\Definition\Repository\AttributesRepository; @@ -12,9 +14,11 @@ use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification; use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory; use CuyZ\Valinor\Utility\Reflection\Reflection; +use ReflectionAttribute; use ReflectionFunction; use ReflectionParameter; +use function array_map; use function str_ends_with; /** @internal */ @@ -52,7 +56,7 @@ public function for(callable $function): FunctionDefinition return new FunctionDefinition( $name, Reflection::signature($reflection), - $this->attributesRepository->for($reflection), + new Attributes(...$this->attributes($reflection)), $reflection->getFileName() ?: null, $class?->name, $reflection->getClosureThis() === null, @@ -79,4 +83,15 @@ private function typeResolver(ReflectionFunction $reflection): ReflectionTypeRes return new ReflectionTypeResolver($nativeParser, $advancedParser); } + + /** + * @return list + */ + private function attributes(ReflectionFunction $reflection): array + { + return array_map( + fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute), + Reflection::attributes($reflection) + ); + } } diff --git a/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php b/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php index cc0d2120..3441b21b 100644 --- a/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php +++ b/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php @@ -4,16 +4,21 @@ namespace CuyZ\Valinor\Definition\Repository\Reflection; +use CuyZ\Valinor\Definition\AttributeDefinition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ParameterDefinition; use CuyZ\Valinor\Definition\Repository\AttributesRepository; use CuyZ\Valinor\Type\Types\UnresolvableType; use CuyZ\Valinor\Utility\Reflection\Reflection; +use ReflectionAttribute; use ReflectionParameter; +use function array_map; + /** @internal */ final class ReflectionParameterDefinitionBuilder { - public function __construct(private AttributesRepository $attributesFactory) {} + public function __construct(private AttributesRepository $attributesRepository) {} public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typeResolver): ParameterDefinition { @@ -22,7 +27,6 @@ public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typ $type = $typeResolver->resolveType($reflection); $isOptional = $reflection->isOptional(); $isVariadic = $reflection->isVariadic(); - $attributes = $this->attributesFactory->for($reflection); if ($reflection->isDefaultValueAvailable()) { $defaultValue = $reflection->getDefaultValue(); @@ -39,6 +43,25 @@ public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typ $type = UnresolvableType::forInvalidParameterDefaultValue($signature, $type, $defaultValue); } - return new ParameterDefinition($name, $signature, $type, $isOptional, $isVariadic, $defaultValue, $attributes); + return new ParameterDefinition( + $name, + $signature, + $type, + $isOptional, + $isVariadic, + $defaultValue, + new Attributes(...$this->attributes($reflection)), + ); + } + + /** + * @return list + */ + private function attributes(ReflectionParameter $reflection): array + { + return array_map( + fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute), + Reflection::attributes($reflection) + ); } } diff --git a/src/Definition/Repository/Reflection/ReflectionPropertyDefinitionBuilder.php b/src/Definition/Repository/Reflection/ReflectionPropertyDefinitionBuilder.php index 802f242c..80f2cf8a 100644 --- a/src/Definition/Repository/Reflection/ReflectionPropertyDefinitionBuilder.php +++ b/src/Definition/Repository/Reflection/ReflectionPropertyDefinitionBuilder.php @@ -4,14 +4,19 @@ namespace CuyZ\Valinor\Definition\Repository\Reflection; +use CuyZ\Valinor\Definition\AttributeDefinition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\PropertyDefinition; use CuyZ\Valinor\Definition\Repository\AttributesRepository; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\NullType; use CuyZ\Valinor\Type\Types\UnresolvableType; use CuyZ\Valinor\Utility\Reflection\Reflection; +use ReflectionAttribute; use ReflectionProperty; +use function array_map; + /** @internal */ final class ReflectionPropertyDefinitionBuilder { @@ -25,7 +30,6 @@ public function for(ReflectionProperty $reflection, ReflectionTypeResolver $type $hasDefaultValue = $this->hasDefaultValue($reflection, $type); $defaultValue = $reflection->getDefaultValue(); $isPublic = $reflection->isPublic(); - $attributes = $this->attributesRepository->for($reflection); if ($hasDefaultValue && ! $type instanceof UnresolvableType @@ -41,7 +45,7 @@ public function for(ReflectionProperty $reflection, ReflectionTypeResolver $type $hasDefaultValue, $defaultValue, $isPublic, - $attributes + new Attributes(...$this->attributes($reflection)), ); } @@ -54,4 +58,15 @@ private function hasDefaultValue(ReflectionProperty $reflection, Type $type): bo return $reflection->getDeclaringClass()->getDefaultProperties()[$reflection->name] !== null || NullType::get()->matches($type); } + + /** + * @return list + */ + private function attributes(ReflectionProperty $reflection): array + { + return array_map( + fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute), + Reflection::attributes($reflection) + ); + } } diff --git a/src/Library/Container.php b/src/Library/Container.php index 281bcf4a..c87823e0 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -9,12 +9,11 @@ use CuyZ\Valinor\Cache\RuntimeCache; use CuyZ\Valinor\Cache\Warmup\RecursiveCacheWarmupService; use CuyZ\Valinor\Definition\FunctionsContainer; -use CuyZ\Valinor\Definition\Repository\AttributesRepository; use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\Cache\CacheFunctionDefinitionRepository; use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; -use CuyZ\Valinor\Definition\Repository\Reflection\NativeAttributesRepository; +use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionAttributesRepository; use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionFunctionDefinitionRepository; use CuyZ\Valinor\Mapper\ArgumentsMapper; @@ -189,9 +188,7 @@ public function __construct(Settings $settings) new ValueTransformersHandler( $this->get(FunctionDefinitionRepository::class), ), - new KeyTransformersHandler( - $this->get(FunctionDefinitionRepository::class), - ), + new KeyTransformersHandler(), $settings->transformersSortedByPriority(), array_keys($settings->transformerAttributes), ), @@ -207,7 +204,6 @@ public function __construct(Settings $settings) ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository( new ReflectionClassDefinitionRepository( $this->get(TypeParserFactory::class), - $this->get(AttributesRepository::class), ), $this->get(CacheInterface::class), ), @@ -215,13 +211,13 @@ public function __construct(Settings $settings) FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository( new ReflectionFunctionDefinitionRepository( $this->get(TypeParserFactory::class), - $this->get(AttributesRepository::class), + new ReflectionAttributesRepository( + $this->get(ClassDefinitionRepository::class), + ), ), $this->get(CacheInterface::class) ), - AttributesRepository::class => fn () => new NativeAttributesRepository(), - TypeParserFactory::class => fn () => new LexingTypeParserFactory(), TypeParser::class => fn () => $this->get(TypeParserFactory::class)->get(), diff --git a/src/Mapper/Object/Argument.php b/src/Mapper/Object/Argument.php index f9747091..71fceb57 100644 --- a/src/Mapper/Object/Argument.php +++ b/src/Mapper/Object/Argument.php @@ -5,7 +5,6 @@ namespace CuyZ\Valinor\Mapper\Object; use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Definition\AttributesContainer; use CuyZ\Valinor\Definition\ParameterDefinition; use CuyZ\Valinor\Definition\PropertyDefinition; use CuyZ\Valinor\Type\Type; @@ -77,6 +76,6 @@ public function isRequired(): bool public function attributes(): Attributes { - return $this->attributes ??= AttributesContainer::empty(); + return $this->attributes ??= Attributes::empty(); } } diff --git a/src/Mapper/Tree/Shell.php b/src/Mapper/Tree/Shell.php index 8d619891..ec308242 100644 --- a/src/Mapper/Tree/Shell.php +++ b/src/Mapper/Tree/Shell.php @@ -5,7 +5,6 @@ namespace CuyZ\Valinor\Mapper\Tree; use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Definition\AttributesContainer; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\UnresolvableType; @@ -95,7 +94,7 @@ public function value(): mixed public function attributes(): Attributes { - return $this->attributes ?? AttributesContainer::empty(); + return $this->attributes ?? Attributes::empty(); } public function path(): string diff --git a/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php b/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php index eec49cd5..6e902e04 100644 --- a/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php +++ b/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php @@ -4,16 +4,16 @@ namespace CuyZ\Valinor\Normalizer\Exception; -use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use LogicException; /** @internal */ final class KeyTransformerHasTooManyParameters extends LogicException { - public function __construct(FunctionDefinition $function) + public function __construct(MethodDefinition $method) { parent::__construct( - "Key transformer must have at most 1 parameter, {$function->parameters()->count()} given for `{$function->signature()}`.", + "Key transformer must have at most 1 parameter, {$method->parameters()->count()} given for `{$method->signature()}`.", 1701701102, ); } diff --git a/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php b/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php index 30c955fc..269c7f7b 100644 --- a/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php +++ b/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php @@ -4,16 +4,16 @@ namespace CuyZ\Valinor\Normalizer\Exception; -use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use LogicException; /** @internal */ final class KeyTransformerParameterInvalidType extends LogicException { - public function __construct(FunctionDefinition $function) + public function __construct(MethodDefinition $method) { parent::__construct( - "Key transformer parameter must be a string, {$function->parameters()->at(0)->type()->toString()} given for `{$function->signature()}`.", + "Key transformer parameter must be a string, {$method->parameters()->at(0)->type()->toString()} given for `{$method->signature()}`.", 1701706316, ); } diff --git a/src/Normalizer/Exception/TransformerHasInvalidCallableParameter.php b/src/Normalizer/Exception/TransformerHasInvalidCallableParameter.php index a9794b12..e3223b87 100644 --- a/src/Normalizer/Exception/TransformerHasInvalidCallableParameter.php +++ b/src/Normalizer/Exception/TransformerHasInvalidCallableParameter.php @@ -5,16 +5,17 @@ namespace CuyZ\Valinor\Normalizer\Exception; use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use CuyZ\Valinor\Type\Type; use LogicException; /** @internal */ final class TransformerHasInvalidCallableParameter extends LogicException { - public function __construct(FunctionDefinition $function, Type $parameterType) + public function __construct(MethodDefinition|FunctionDefinition $method, Type $parameterType) { parent::__construct( - "Transformer's second parameter must be a callable, `{$parameterType->toString()}` given for `{$function->signature()}`.", + "Transformer's second parameter must be a callable, `{$parameterType->toString()}` given for `{$method->signature()}`.", 1695065710, ); } diff --git a/src/Normalizer/Exception/TransformerHasNoParameter.php b/src/Normalizer/Exception/TransformerHasNoParameter.php index 272e4412..27127aad 100644 --- a/src/Normalizer/Exception/TransformerHasNoParameter.php +++ b/src/Normalizer/Exception/TransformerHasNoParameter.php @@ -5,15 +5,16 @@ namespace CuyZ\Valinor\Normalizer\Exception; use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use LogicException; /** @internal */ final class TransformerHasNoParameter extends LogicException { - public function __construct(FunctionDefinition $function) + public function __construct(MethodDefinition|FunctionDefinition $method) { parent::__construct( - "Transformer must have at least one parameter, none given for `{$function->signature()}`.", + "Transformer must have at least one parameter, none given for `{$method->signature()}`.", 1695064946, ); } diff --git a/src/Normalizer/Exception/TransformerHasTooManyParameters.php b/src/Normalizer/Exception/TransformerHasTooManyParameters.php index f8291094..6da2055b 100644 --- a/src/Normalizer/Exception/TransformerHasTooManyParameters.php +++ b/src/Normalizer/Exception/TransformerHasTooManyParameters.php @@ -5,15 +5,16 @@ namespace CuyZ\Valinor\Normalizer\Exception; use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use LogicException; /** @internal */ final class TransformerHasTooManyParameters extends LogicException { - public function __construct(FunctionDefinition $function) + public function __construct(MethodDefinition|FunctionDefinition $method) { parent::__construct( - "Transformer must have at most 2 parameters, {$function->parameters()->count()} given for `{$function->signature()}`.", + "Transformer must have at most 2 parameters, {$method->parameters()->count()} given for `{$method->signature()}`.", 1695065433, ); } diff --git a/src/Normalizer/Transformer/KeyTransformersHandler.php b/src/Normalizer/Transformer/KeyTransformersHandler.php index 3c6c2d67..67b907b5 100644 --- a/src/Normalizer/Transformer/KeyTransformersHandler.php +++ b/src/Normalizer/Transformer/KeyTransformersHandler.php @@ -4,8 +4,8 @@ namespace CuyZ\Valinor\Normalizer\Transformer; -use CuyZ\Valinor\Definition\FunctionDefinition; -use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; +use CuyZ\Valinor\Definition\AttributeDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use CuyZ\Valinor\Normalizer\Exception\KeyTransformerHasTooManyParameters; use CuyZ\Valinor\Normalizer\Exception\KeyTransformerParameterInvalidType; use CuyZ\Valinor\Type\StringType; @@ -16,53 +16,48 @@ final class KeyTransformersHandler /** @var array */ private array $keyTransformerCheck = []; - public function __construct( - private FunctionDefinitionRepository $functionDefinitionRepository, - ) {} - /** - * @param list $attributes + * @param list $attributes */ public function transformKey(string|int $key, array $attributes): string|int { foreach ($attributes as $attribute) { - if (! method_exists($attribute, 'normalizeKey')) { + if (! $attribute->class()->methods()->has('normalizeKey')) { continue; } - // PHP8.1 First-class callable syntax - $function = $this->functionDefinitionRepository->for([$attribute, 'normalizeKey']); + $method = $attribute->class()->methods()->get('normalizeKey'); - $this->checkKeyTransformer($function); + $this->checkKeyTransformer($method); - if ($function->parameters()->count() === 0 || $function->parameters()->at(0)->type()->accepts($key)) { - $key = $attribute->normalizeKey($key); + if ($method->parameters()->count() === 0 || $method->parameters()->at(0)->type()->accepts($key)) { + $key = $attribute->instantiate()->normalizeKey($key); // @phpstan-ignore-line / We know the method exists } } return $key; } - private function checkKeyTransformer(FunctionDefinition $function): void + private function checkKeyTransformer(MethodDefinition $method): void { - if (isset($this->keyTransformerCheck[$function->signature()])) { + if (isset($this->keyTransformerCheck[$method->signature()])) { return; } // @infection-ignore-all - $this->keyTransformerCheck[$function->signature()] = true; + $this->keyTransformerCheck[$method->signature()] = true; - $parameters = $function->parameters(); + $parameters = $method->parameters(); if ($parameters->count() > 1) { - throw new KeyTransformerHasTooManyParameters($function); + throw new KeyTransformerHasTooManyParameters($method); } if ($parameters->count() > 0) { $type = $parameters->at(0)->type(); if (! $type instanceof StringType) { - throw new KeyTransformerParameterInvalidType($function); + throw new KeyTransformerParameterInvalidType($method); } } } diff --git a/src/Normalizer/Transformer/RecursiveTransformer.php b/src/Normalizer/Transformer/RecursiveTransformer.php index 626c2ef2..a0803c73 100644 --- a/src/Normalizer/Transformer/RecursiveTransformer.php +++ b/src/Normalizer/Transformer/RecursiveTransformer.php @@ -6,6 +6,7 @@ use BackedEnum; use Closure; +use CuyZ\Valinor\Definition\AttributeDefinition; use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository; use CuyZ\Valinor\Normalizer\Exception\CircularReferenceFoundDuringNormalization; @@ -22,6 +23,7 @@ use function array_map; use function array_reverse; use function get_object_vars; +use function is_a; use function is_array; use function is_iterable; @@ -48,7 +50,7 @@ public function transform(mixed $value): mixed /** * @param WeakMap $references - * @param list $attributes + * @param list $attributes * @return iterable|scalar|null */ private function doTransform(mixed $value, WeakMap $references, array $attributes = []): mixed @@ -68,6 +70,7 @@ private function doTransform(mixed $value, WeakMap $references, array $attribute if ($this->transformerAttributes !== [] && is_object($value)) { $classAttributes = $this->classDefinitionRepository->for(NativeClassType::for($value::class))->attributes(); + $classAttributes = $this->filterAttributes($classAttributes); $attributes = [...$attributes, ...$classAttributes]; } @@ -146,7 +149,7 @@ private function defaultTransformer(mixed $value, WeakMap $references): mixed $class = $this->classDefinitionRepository->for(NativeClassType::for($value::class)); foreach ($values as $key => $subValue) { - $attributes = $this->filterAttributes($class->properties()->get($key)->attributes()); + $attributes = $this->filterAttributes($class->properties()->get($key)->attributes())->toArray(); $key = $this->keyTransformers->transformKey($key, $attributes); @@ -174,22 +177,16 @@ private function defaultTransformer(mixed $value, WeakMap $references): mixed throw new TypeUnhandledByNormalizer($value); } - /** - * @return list - */ - private function filterAttributes(Attributes $attributes): array + private function filterAttributes(Attributes $attributes): Attributes { - $filteredAttributes = []; - - foreach ($attributes as $attribute) { + return $attributes->filter(function (AttributeDefinition $attribute) { foreach ($this->transformerAttributes as $transformerAttribute) { - if ($attribute instanceof $transformerAttribute) { - $filteredAttributes[] = $attribute; - break; + if (is_a($attribute->class()->type()->className(), $transformerAttribute, true)) { + return true; } } - } - return $filteredAttributes; + return false; + }); } } diff --git a/src/Normalizer/Transformer/ValueTransformersHandler.php b/src/Normalizer/Transformer/ValueTransformersHandler.php index ad5b629f..31731336 100644 --- a/src/Normalizer/Transformer/ValueTransformersHandler.php +++ b/src/Normalizer/Transformer/ValueTransformersHandler.php @@ -4,7 +4,9 @@ namespace CuyZ\Valinor\Normalizer\Transformer; +use CuyZ\Valinor\Definition\AttributeDefinition; use CuyZ\Valinor\Definition\FunctionDefinition; +use CuyZ\Valinor\Definition\MethodDefinition; use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; use CuyZ\Valinor\Normalizer\Exception\TransformerHasInvalidCallableParameter; use CuyZ\Valinor\Normalizer\Exception\TransformerHasNoParameter; @@ -13,7 +15,6 @@ use function array_shift; use function call_user_func; -use function method_exists; /** @internal */ final class ValueTransformersHandler @@ -26,7 +27,7 @@ public function __construct( ) {} /** - * @param array $attributes + * @param array $attributes * @param list $transformers * @return array|scalar|null */ @@ -39,7 +40,7 @@ public function transform(mixed $value, array $attributes, array $transformers, /** * @param list $transformers - * @param list $attributes + * @param array $attributes */ private function next(array $transformers, mixed $value, array $attributes, callable $defaultTransformer): callable { @@ -69,7 +70,7 @@ private function next(array $transformers, mixed $value, array $attributes, call } /** - * @param array $attributes + * @param array $attributes */ private function nextAttribute(mixed $value, array $attributes, callable $next): callable { @@ -79,43 +80,46 @@ private function nextAttribute(mixed $value, array $attributes, callable $next): return $next; } - if (! method_exists($attribute, 'normalize')) { + if (! $attribute->class()->methods()->has('normalize')) { return $this->nextAttribute($value, $attributes, $next); } - // PHP8.1 First-class callable syntax - $function = $this->functionDefinitionRepository->for([$attribute, 'normalize']); + $method = $attribute->class()->methods()->get('normalize'); - $this->checkTransformer($function); + $this->checkTransformer($method); - if (! $function->parameters()->at(0)->type()->accepts($value)) { + if (! $method->parameters()->at(0)->type()->accepts($value)) { return $this->nextAttribute($value, $attributes, $next); } - return fn () => $attribute->normalize($value, fn () => call_user_func($this->nextAttribute($value, $attributes, $next))); + // @phpstan-ignore-next-line / We know the method exists + return fn () => $attribute->instantiate()->normalize( + $value, + fn () => call_user_func($this->nextAttribute($value, $attributes, $next)) + ); } - private function checkTransformer(FunctionDefinition $function): void + private function checkTransformer(MethodDefinition|FunctionDefinition $method): void { - if (isset($this->transformerCheck[$function->signature()])) { + if (isset($this->transformerCheck[$method->signature()])) { return; } // @infection-ignore-all - $this->transformerCheck[$function->signature()] = true; + $this->transformerCheck[$method->signature()] = true; - $parameters = $function->parameters(); + $parameters = $method->parameters(); if ($parameters->count() === 0) { - throw new TransformerHasNoParameter($function); + throw new TransformerHasNoParameter($method); } if ($parameters->count() > 2) { - throw new TransformerHasTooManyParameters($function); + throw new TransformerHasTooManyParameters($method); } if ($parameters->count() > 1 && ! $parameters->at(1)->type() instanceof CallableType) { - throw new TransformerHasInvalidCallableParameter($function, $parameters->at(1)->type()); + throw new TransformerHasInvalidCallableParameter($method, $parameters->at(1)->type()); } } } diff --git a/src/Utility/Reflection/Reflection.php b/src/Utility/Reflection/Reflection.php index 307f7f51..d3d4e3d6 100644 --- a/src/Utility/Reflection/Reflection.php +++ b/src/Utility/Reflection/Reflection.php @@ -4,7 +4,10 @@ namespace CuyZ\Valinor\Utility\Reflection; +use Attribute; use Closure; +use Error; +use ReflectionAttribute; use ReflectionClass; use ReflectionFunction; use ReflectionIntersectionType; @@ -17,6 +20,8 @@ use Reflector; use RuntimeException; +use function array_filter; +use function array_map; use function class_exists; use function implode; use function interface_exists; @@ -60,6 +65,39 @@ public static function function(callable $function): ReflectionFunction return self::$functionReflection[spl_object_hash($closure)] ??= new ReflectionFunction($closure); } + /** + * @param ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection + * @return array> + */ + public static function attributes(Reflector $reflection): array + { + $attributes = array_filter( + $reflection->getAttributes(), + static fn (ReflectionAttribute $attribute) => $attribute->getName() !== Attribute::class, + ); + + return array_filter( + array_map( + static function (ReflectionAttribute $attribute) { + try { + $attribute->newInstance(); + + return $attribute; + } catch (Error) { + // Race condition when the attribute is affected to a property/parameter + // that was PROMOTED, in this case the attribute will be applied to both + // ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute + // class is configured to support only ONE of them (parameter OR property) + // https://wiki.php.net/rfc/constructor_promotion#attributes for more details. + // Ignore attribute if the instantiation failed. + return null; + } + }, + $attributes, + ), + ); + } + public static function signature(Reflector $reflection): string { if ($reflection instanceof ReflectionClass) { diff --git a/tests/Fake/Definition/FakeAttributeDefinition.php b/tests/Fake/Definition/FakeAttributeDefinition.php new file mode 100644 index 00000000..21a47f9b --- /dev/null +++ b/tests/Fake/Definition/FakeAttributeDefinition.php @@ -0,0 +1,37 @@ + $reflection + */ + public static function fromReflection(ReflectionAttribute $reflection): AttributeDefinition + { + $classReflection = new ReflectionClass($reflection->getName()); + + return new AttributeDefinition( + FakeClassDefinition::fromReflection($classReflection), + $reflection->getArguments(), + ); + } +} diff --git a/tests/Fake/Definition/FakeAttributes.php b/tests/Fake/Definition/FakeAttributes.php index 9d3e2c30..442e948a 100644 --- a/tests/Fake/Definition/FakeAttributes.php +++ b/tests/Fake/Definition/FakeAttributes.php @@ -5,44 +5,27 @@ namespace CuyZ\Valinor\Tests\Fake\Definition; use CuyZ\Valinor\Definition\Attributes; -use stdClass; -use Traversable; - -use function array_filter; -use function count; - -final class FakeAttributes implements Attributes +use CuyZ\Valinor\Utility\Reflection\Reflection; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionParameter; +use ReflectionProperty; +use Reflector; +use ReflectionAttribute; + +final class FakeAttributes { - /** @var array */ - private array $attributes; - - public function __construct(object ...$attributes) - { - $this->attributes = $attributes; - } - - public static function notEmpty(): self - { - return new self(new stdClass()); - } - - public function has(string $className): bool - { - return $this->ofType($className) !== []; - } - - public function ofType(string $className): array - { - return array_filter($this->attributes, fn (object $attribute) => $attribute instanceof $className); - } - - public function count(): int - { - return count($this->attributes); - } - - public function getIterator(): Traversable + /** + * @param ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection + */ + public static function fromReflection(Reflector $reflection): Attributes { - yield from $this->attributes; + return new Attributes( + ...array_map( + static fn (ReflectionAttribute $reflection) => FakeAttributeDefinition::fromReflection($reflection), + Reflection::attributes($reflection), + ), + ); } } diff --git a/tests/Fake/Definition/FakeClassDefinition.php b/tests/Fake/Definition/FakeClassDefinition.php index a4d997a4..a7b57803 100644 --- a/tests/Fake/Definition/FakeClassDefinition.php +++ b/tests/Fake/Definition/FakeClassDefinition.php @@ -4,6 +4,7 @@ namespace CuyZ\Valinor\Tests\Fake\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\Methods; use CuyZ\Valinor\Definition\Properties; @@ -26,7 +27,7 @@ public static function new(string $name = stdClass::class): ClassDefinition { return new ClassDefinition( new NativeClassType($name), - new FakeAttributes(), + new Attributes(), new Properties(), new Methods(), true, @@ -51,7 +52,7 @@ public static function fromReflection(ReflectionClass $reflection): ClassDefinit return new ClassDefinition( new NativeClassType($reflection->name), - new FakeAttributes(), + new Attributes(), new Properties(...$properties), new Methods(...$methods), $reflection->isFinal(), diff --git a/tests/Fake/Definition/FakeFunctionDefinition.php b/tests/Fake/Definition/FakeFunctionDefinition.php index 6d27388f..259a4be7 100644 --- a/tests/Fake/Definition/FakeFunctionDefinition.php +++ b/tests/Fake/Definition/FakeFunctionDefinition.php @@ -4,6 +4,7 @@ namespace CuyZ\Valinor\Tests\Fake\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Definition\ParameterDefinition; use CuyZ\Valinor\Definition\Parameters; @@ -17,7 +18,7 @@ public static function new(string $fileName = null): FunctionDefinition return new FunctionDefinition( 'foo', 'foo:42-1337', - new FakeAttributes(), + new Attributes(), $fileName ?? 'foo/bar', stdClass::class, true, @@ -30,7 +31,7 @@ public static function new(string $fileName = null): FunctionDefinition false, false, 'foo', - new FakeAttributes() + new Attributes() ) ), NativeStringType::get() diff --git a/tests/Fake/Definition/FakeMethodDefinition.php b/tests/Fake/Definition/FakeMethodDefinition.php index cdfb74e2..9a959297 100644 --- a/tests/Fake/Definition/FakeMethodDefinition.php +++ b/tests/Fake/Definition/FakeMethodDefinition.php @@ -7,6 +7,7 @@ use CuyZ\Valinor\Definition\MethodDefinition; use CuyZ\Valinor\Definition\Parameters; use CuyZ\Valinor\Tests\Fake\Type\FakeType; +use CuyZ\Valinor\Type\Types\MixedType; use ReflectionMethod; use ReflectionParameter; @@ -35,7 +36,7 @@ public static function constructor(): MethodDefinition public static function fromReflection(ReflectionMethod $reflection): MethodDefinition { - $returnType = new FakeType(); + $returnType = new MixedType(); if ($reflection->hasReturnType()) { $returnType = FakeType::from($reflection->getReturnType()->getName()); // @phpstan-ignore-line diff --git a/tests/Fake/Definition/FakeParameterDefinition.php b/tests/Fake/Definition/FakeParameterDefinition.php index e6c9d07c..8ad25055 100644 --- a/tests/Fake/Definition/FakeParameterDefinition.php +++ b/tests/Fake/Definition/FakeParameterDefinition.php @@ -4,6 +4,7 @@ namespace CuyZ\Valinor\Tests\Fake\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ParameterDefinition; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use CuyZ\Valinor\Type\Type; @@ -22,7 +23,7 @@ public static function new(string $name = 'someParameter', Type $type = null): P false, false, null, - new FakeAttributes() + new Attributes() ); } @@ -35,7 +36,7 @@ public static function optional(string $name, Type $type, mixed $defaultValue): true, false, $defaultValue, - new FakeAttributes() + new Attributes() ); } @@ -54,7 +55,7 @@ public static function fromReflection(ReflectionParameter $reflection): Paramete $reflection->isOptional(), $reflection->isVariadic(), $reflection->isDefaultValueAvailable() ? $reflection->getDefaultValue() : null, - new FakeAttributes() + new Attributes() ); } } diff --git a/tests/Fake/Definition/FakePropertyDefinition.php b/tests/Fake/Definition/FakePropertyDefinition.php index fdb54630..848e16bc 100644 --- a/tests/Fake/Definition/FakePropertyDefinition.php +++ b/tests/Fake/Definition/FakePropertyDefinition.php @@ -4,9 +4,10 @@ namespace CuyZ\Valinor\Tests\Fake\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\PropertyDefinition; use CuyZ\Valinor\Tests\Fake\Type\FakeType; -use CuyZ\Valinor\Type\Type; +use CuyZ\Valinor\Type\Types\MixedType; use ReflectionProperty; final class FakePropertyDefinition @@ -18,18 +19,18 @@ public static function new(string $name = 'someProperty'): PropertyDefinition return new PropertyDefinition( $name, $name, - new FakeType(), + new MixedType(), false, null, false, - new FakeAttributes() + new Attributes() ); } public static function fromReflection(ReflectionProperty $reflection): PropertyDefinition { $defaultProperties = $reflection->getDeclaringClass()->getDefaultProperties(); - $type = new FakeType(); + $type = new MixedType(); if ($reflection->hasType()) { $type = FakeType::from($reflection->getType()->getName()); // @phpstan-ignore-line @@ -42,20 +43,7 @@ public static function fromReflection(ReflectionProperty $reflection): PropertyD isset($defaultProperties[$reflection->name]), $defaultProperties[$reflection->name] ?? null, $reflection->isPublic(), - new FakeAttributes() - ); - } - - public static function withType(Type $type): PropertyDefinition - { - return new PropertyDefinition( - 'someProperty', - 'someProperty', - $type, - false, - null, - false, - new FakeAttributes() + new Attributes() ); } } diff --git a/tests/Fake/Definition/Repository/FakeAttributesRepository.php b/tests/Fake/Definition/Repository/FakeAttributesRepository.php index c7fe5337..e042abf6 100644 --- a/tests/Fake/Definition/Repository/FakeAttributesRepository.php +++ b/tests/Fake/Definition/Repository/FakeAttributesRepository.php @@ -4,15 +4,15 @@ namespace CuyZ\Valinor\Tests\Fake\Definition\Repository; -use CuyZ\Valinor\Definition\Attributes; +use CuyZ\Valinor\Definition\AttributeDefinition; use CuyZ\Valinor\Definition\Repository\AttributesRepository; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; -use Reflector; +use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributeDefinition; +use ReflectionAttribute; final class FakeAttributesRepository implements AttributesRepository { - public function for(Reflector $reflector): Attributes + public function for(ReflectionAttribute $reflection): AttributeDefinition { - return new FakeAttributes(); + return FakeAttributeDefinition::new(); } } diff --git a/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php b/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php index c1db9156..0ffba879 100644 --- a/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php +++ b/tests/Fake/Mapper/Tree/Builder/FakeTreeNode.php @@ -7,7 +7,6 @@ use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode; use CuyZ\Valinor\Mapper\Tree\Message\Message; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Mapper\FakeShell; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Type\FakeType; @@ -41,7 +40,7 @@ public static function branch(array $children, Type $type = null, mixed $value = $childShell = $shell->child( $child['name'] ?? (string)$key, $child['type'] ?? FakeType::permissive(), - $child['attributes'] ?? new FakeAttributes(), + $child['attributes'] ?? new Attributes(), )->withValue($childValue); $node = TreeNode::leaf($childShell, $childValue); diff --git a/tests/Fake/Type/FakeType.php b/tests/Fake/Type/FakeType.php index 4d8e5fa0..e3548887 100644 --- a/tests/Fake/Type/FakeType.php +++ b/tests/Fake/Type/FakeType.php @@ -7,6 +7,7 @@ use CuyZ\Valinor\Tests\Fixture\Object\StringableObject; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ArrayKeyType; +use CuyZ\Valinor\Type\Types\ArrayType; use CuyZ\Valinor\Type\Types\NativeClassType; use CuyZ\Valinor\Type\Types\NativeBooleanType; use CuyZ\Valinor\Type\Types\MixedType; @@ -43,6 +44,10 @@ public static function from(string $raw): Type return NativeBooleanType::get(); } + if ($raw === 'array') { + return ArrayType::native(); + } + if ($raw === 'array-key') { return ArrayKeyType::default(); } diff --git a/tests/Functional/Definition/Repository/Cache/Compiler/AttributesCompilerTest.php b/tests/Functional/Definition/Repository/Cache/Compiler/AttributesCompilerTest.php index 994312f9..b7a4db67 100644 --- a/tests/Functional/Definition/Repository/Cache/Compiler/AttributesCompilerTest.php +++ b/tests/Functional/Definition/Repository/Cache/Compiler/AttributesCompilerTest.php @@ -2,11 +2,10 @@ namespace CuyZ\Valinor\Tests\Functional\Definition\Repository\Cache\Compiler; -use AssertionError; +use CuyZ\Valinor\Definition\AttributeDefinition; use CuyZ\Valinor\Definition\Attributes; -use CuyZ\Valinor\Definition\AttributesContainer; -use CuyZ\Valinor\Definition\NativeAttributes; use CuyZ\Valinor\Definition\Repository\Cache\Compiler\AttributesCompiler; +use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler; use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fixture\Attribute\AttributeWithArguments; use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute; @@ -17,6 +16,7 @@ use Error; use PHPUnit\Framework\TestCase; use ReflectionClass; +use ReflectionMethod; use ReflectionParameter; use ReflectionProperty; @@ -26,48 +26,55 @@ final class AttributesCompilerTest extends TestCase protected function setUp(): void { - $this->attributesCompiler = new AttributesCompiler(); + $this->attributesCompiler = new AttributesCompiler(new ClassDefinitionCompiler()); } public function test_compiles_an_empty_attributes_instance_when_no_attributes_are_given(): void { - $attributes = $this->compile(new FakeAttributes()); + $attributes = $this->compile(Attributes::empty()); - self::assertSame(AttributesContainer::empty(), $attributes); + self::assertSame(Attributes::empty(), $attributes); } - public function test_compiles_native_php_attributes_for_class_with_attributes(): void + public function test_compiles_attributes_for_class_with_attributes(): void { $reflection = new ReflectionClass(ObjectWithAttributes::class); + $attributes = FakeAttributes::fromReflection($reflection); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = $this->compile($attributes); self::assertCount(2, $attributes); self::assertTrue($attributes->has(BasicAttribute::class)); self::assertTrue($attributes->has(AttributeWithArguments::class)); - $attribute = [...$attributes->ofType(AttributeWithArguments::class)][0]; + /** @var AttributeWithArguments $attribute */ + $attribute = $attributes->filter( + fn (AttributeDefinition $attribute) => $attribute->class()->type()->className() === AttributeWithArguments::class + )->toArray()[0]->instantiate(); self::assertSame('foo', $attribute->foo); self::assertSame('bar', $attribute->bar); } - public function test_compiles_native_php_attributes_for_class_without_attributes(): void + public function test_compiles_attributes_for_class_without_attributes(): void { $reflection = new ReflectionClass(new class () {}); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = FakeAttributes::fromReflection($reflection); + + $attributes = $this->compile($attributes); - self::assertSame(AttributesContainer::empty(), $attributes); + self::assertSame(Attributes::empty(), $attributes); } /** * @requires PHP >= 8.1 */ - public function test_compiles_native_php_attributes_for_class_with_nested_attributes(): void + public function test_compiles_attributes_for_class_with_nested_attributes(): void { $reflection = new ReflectionClass(ObjectWithNestedAttributes::class); + $attributes = FakeAttributes::fromReflection($reflection); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = $this->compile($attributes); self::assertCount(3, $attributes); self::assertTrue($attributes->has(BasicAttribute::class)); @@ -75,12 +82,17 @@ public function test_compiles_native_php_attributes_for_class_with_nested_attrib self::assertTrue($attributes->has(NestedAttribute::class)); /** @var AttributeWithArguments $attribute */ - $attribute = [...$attributes->ofType(AttributeWithArguments::class)][0]; + $attribute = $attributes->filter( + fn (AttributeDefinition $attribute) => $attribute->class()->type()->className() === AttributeWithArguments::class + )->toArray()[0]->instantiate(); self::assertSame('foo', $attribute->foo); self::assertSame('bar', $attribute->bar); - $attribute = [...$attributes->ofType(NestedAttribute::class)][0]; + /** @var NestedAttribute $attribute */ + $attribute = $attributes->filter( + fn (AttributeDefinition $attribute) => $attribute->class()->type()->className() === NestedAttribute::class + )->toArray()[0]->instantiate(); self::assertCount(2, $attribute->nestedAttributes); self::assertInstanceOf(BasicAttribute::class, $attribute->nestedAttributes[0]); @@ -96,12 +108,12 @@ public function test_compiles_native_php_attributes_for_class_with_nested_attrib /** * @requires PHP >= 8.1 */ - public function test_compiles_native_php_attributes_for_property_with_nested_attributes(): void + public function test_compiles_attributes_for_property_with_nested_attributes(): void { - $reflection = new ReflectionClass(ObjectWithNestedAttributes::class); - $reflection = $reflection->getProperty('property'); + $reflection = new ReflectionProperty(ObjectWithNestedAttributes::class, 'property'); + $attributes = FakeAttributes::fromReflection($reflection); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = $this->compile($attributes); self::assertCount(3, $attributes); self::assertTrue($attributes->has(BasicAttribute::class)); @@ -112,12 +124,12 @@ public function test_compiles_native_php_attributes_for_property_with_nested_att /** * @requires PHP >= 8.1 */ - public function test_compiles_native_php_attributes_for_method_with_nested_attributes(): void + public function test_compiles_attributes_for_method_with_nested_attributes(): void { - $reflection = new ReflectionClass(ObjectWithNestedAttributes::class); - $reflection = $reflection->getMethod('method'); + $reflection = new ReflectionMethod(ObjectWithNestedAttributes::class, 'method'); + $attributes = FakeAttributes::fromReflection($reflection); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = $this->compile($attributes); self::assertCount(3, $attributes); self::assertTrue($attributes->has(BasicAttribute::class)); @@ -125,10 +137,12 @@ public function test_compiles_native_php_attributes_for_method_with_nested_attri self::assertTrue($attributes->has(NestedAttribute::class)); } - public function test_compiles_native_php_attributes_for_promoted_property_with_property_target_attribute(): void + public function test_compiles_attributes_for_promoted_property_with_property_target_attribute(): void { $reflection = new ReflectionProperty(ObjectWithAttributes::class, 'promotedProperty'); - $attributes = $this->compile(new NativeAttributes($reflection)); + $attributes = FakeAttributes::fromReflection($reflection); + + $attributes = $this->compile($attributes); self::assertCount(1, $attributes); self::assertTrue($attributes->has(PropertyTargetAttribute::class)); @@ -137,16 +151,11 @@ public function test_compiles_native_php_attributes_for_promoted_property_with_p public function test_compiles_an_empty_attributes_instance_for_promoted_parameter_with_property_target_attribute(): void { $reflection = new ReflectionParameter([ObjectWithAttributes::class, '__construct'], 'promotedProperty'); - $attributes = $this->compile(new NativeAttributes($reflection)); - - self::assertSame(AttributesContainer::empty(), $attributes); - } + $attributes = FakeAttributes::fromReflection($reflection); - public function test_invalid_attributes_instance_throws_assertion_error(): void - { - $this->expectException(AssertionError::class); + $attributes = $this->compile($attributes); - $this->compile(FakeAttributes::notEmpty()); + self::assertSame(Attributes::empty(), $attributes); } private function compile(Attributes $attributes): Attributes diff --git a/tests/Functional/Definition/Repository/Cache/Compiler/FunctionDefinitionCompilerTest.php b/tests/Functional/Definition/Repository/Cache/Compiler/FunctionDefinitionCompilerTest.php index 0b775e2d..e3f727a1 100644 --- a/tests/Functional/Definition/Repository/Cache/Compiler/FunctionDefinitionCompilerTest.php +++ b/tests/Functional/Definition/Repository/Cache/Compiler/FunctionDefinitionCompilerTest.php @@ -4,11 +4,11 @@ namespace CuyZ\Valinor\Tests\Functional\Definition\Repository\Cache\Compiler; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Definition\ParameterDefinition; use CuyZ\Valinor\Definition\Parameters; use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Type\Types\NativeStringType; use Error; use PHPUnit\Framework\TestCase; @@ -30,7 +30,7 @@ public function test_function_is_compiled_correctly(): void $function = new FunctionDefinition( 'foo', 'foo:42-1337', - new FakeAttributes(), + new Attributes(), 'foo/bar', stdClass::class, true, @@ -43,7 +43,7 @@ public function test_function_is_compiled_correctly(): void false, false, 'foo', - new FakeAttributes() + new Attributes() ) ), NativeStringType::get() diff --git a/tests/Unit/Definition/AttributesContainerTest.php b/tests/Unit/Definition/AttributesContainerTest.php deleted file mode 100644 index 73a841c9..00000000 --- a/tests/Unit/Definition/AttributesContainerTest.php +++ /dev/null @@ -1,87 +0,0 @@ -has(BasicAttribute::class)); - self::assertEmpty($attributes->ofType(BasicAttribute::class)); - } - - public function test_attributes_are_countable(): void - { - $attributes = [ - $this->attribute(new stdClass()), - $this->attribute(new stdClass()), - $this->attribute(new stdClass()), - ]; - - $container = new AttributesContainer(...$attributes); - - self::assertCount(3, $container); - } - - public function test_attributes_are_traversable(): void - { - $objects = [new stdClass(), new stdClass(), new stdClass()]; - $attributes = [ - $this->attribute($objects[0]), - $this->attribute($objects[1]), - $this->attribute($objects[2]), - ]; - - $container = new AttributesContainer(...$attributes); - - self::assertSame($objects, iterator_to_array($container)); - } - - public function test_attributes_has_type_checks_all_attributes(): void - { - $attributes = new AttributesContainer($this->attribute(new stdClass())); - - self::assertTrue($attributes->has(stdClass::class)); - self::assertFalse($attributes->has(DateTimeInterface::class)); - } - - public function test_attributes_of_type_filters_on_given_class_name(): void - { - $object = new stdClass(); - $date = new DateTime(); - - $attributes = new AttributesContainer($this->attribute($object), $this->attribute($date)); - $filteredAttributes = $attributes->ofType(DateTimeInterface::class); - - self::assertContainsEquals($date, $filteredAttributes); - self::assertNotContains($object, $filteredAttributes); - self::assertSame($date, $filteredAttributes[0]); - } - - /** - * @return AttributeParam - */ - private function attribute(object $object): array - { - return [ - 'class'=> $object::class, - 'callback' => fn () => $object - ]; - } -} diff --git a/tests/Unit/Definition/AttributesTest.php b/tests/Unit/Definition/AttributesTest.php new file mode 100644 index 00000000..31b052a3 --- /dev/null +++ b/tests/Unit/Definition/AttributesTest.php @@ -0,0 +1,73 @@ +has(BasicAttribute::class)); + self::assertSame([], $attributes->toArray()); + } + + public function test_attributes_are_countable(): void + { + $container = new Attributes( + FakeAttributeDefinition::new(), + FakeAttributeDefinition::new(), + FakeAttributeDefinition::new(), + ); + + self::assertCount(3, $container); + } + + public function test_attributes_are_traversable(): void + { + $attributes = [ + FakeAttributeDefinition::new(), + FakeAttributeDefinition::new(), + FakeAttributeDefinition::new(), + ]; + + $container = new Attributes(...$attributes); + + self::assertSame($attributes, iterator_to_array($container)); + self::assertSame($attributes, $container->toArray()); + } + + public function test_attributes_has_type_checks_all_attributes(): void + { + $attributes = new Attributes(FakeAttributeDefinition::new(DateTimeImmutable::class)); + + self::assertTrue($attributes->has(DateTimeInterface::class)); + self::assertFalse($attributes->has(stdClass::class)); + } + + public function test_attributes_of_type_filters_on_given_class_name(): void + { + $attributeA = FakeAttributeDefinition::new(); + $attributeB = FakeAttributeDefinition::new(DateTimeImmutable::class); + + $attributes = new Attributes($attributeA, $attributeB); + $filteredAttributes = $attributes->filter(fn (AttributeDefinition $attribute) => $attribute->class()->type()->className() === DateTimeImmutable::class); + + self::assertContainsEquals($attributeB, $filteredAttributes); + self::assertNotContains($attributeA, $filteredAttributes); + self::assertSame($attributeB, $filteredAttributes->toArray()[0]); + } +} diff --git a/tests/Unit/Definition/ClassDefinitionTest.php b/tests/Unit/Definition/ClassDefinitionTest.php index 2f44c55a..6a9909a9 100644 --- a/tests/Unit/Definition/ClassDefinitionTest.php +++ b/tests/Unit/Definition/ClassDefinitionTest.php @@ -4,10 +4,10 @@ namespace CuyZ\Valinor\Tests\Unit\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\Methods; use CuyZ\Valinor\Definition\Properties; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Definition\FakeMethodDefinition; use CuyZ\Valinor\Tests\Fake\Definition\FakePropertyDefinition; use CuyZ\Valinor\Tests\Fake\Type\FakeType; @@ -20,7 +20,7 @@ final class ClassDefinitionTest extends TestCase public function test_class_data_can_be_retrieved(): void { $type = new NativeClassType(stdClass::class, ['T' => new FakeType()]); - $attributes = new FakeAttributes(); + $attributes = new Attributes(); $properties = new Properties(FakePropertyDefinition::new()); $methods = new Methods(FakeMethodDefinition::new()); diff --git a/tests/Unit/Definition/NativeAttributesTest.php b/tests/Unit/Definition/NativeAttributesTest.php deleted file mode 100644 index 9258f4af..00000000 --- a/tests/Unit/Definition/NativeAttributesTest.php +++ /dev/null @@ -1,105 +0,0 @@ -has(BasicAttribute::class)); - self::assertEmpty($attributes->ofType(BasicAttribute::class)); - } - } - - public function test_class_attributes_are_fetched_correctly(): void - { - $reflection = new ReflectionClass(ObjectWithAttributes::class); - $attributes = new NativeAttributes($reflection); - - self::assertCount(2, $attributes); - self::assertTrue($attributes->has(BasicAttribute::class)); - self::assertTrue($attributes->has(AttributeWithArguments::class)); - self::assertCount(1, $attributes->ofType(BasicAttribute::class)); - self::assertCount(1, $attributes->ofType(AttributeWithArguments::class)); - } - - public function test_property_attributes_are_fetched_correctly(): void - { - $reflection = new ReflectionProperty(ObjectWithAttributes::class, 'property'); - $attributes = new NativeAttributes($reflection); - - self::assertCount(2, $attributes); - self::assertTrue($attributes->has(BasicAttribute::class)); - self::assertTrue($attributes->has(AttributeWithArguments::class)); - self::assertCount(1, $attributes->ofType(BasicAttribute::class)); - self::assertCount(1, $attributes->ofType(AttributeWithArguments::class)); - } - - public function test_method_attributes_are_fetched_correctly(): void - { - $reflection = new ReflectionMethod(ObjectWithAttributes::class, 'method'); - $attributes = new NativeAttributes($reflection); - - self::assertCount(2, $attributes); - self::assertTrue($attributes->has(BasicAttribute::class)); - self::assertTrue($attributes->has(AttributeWithArguments::class)); - self::assertCount(1, $attributes->ofType(BasicAttribute::class)); - self::assertCount(1, $attributes->ofType(AttributeWithArguments::class)); - } - - public function test_parameter_attributes_are_fetched_correctly(): void - { - $reflection = new ReflectionParameter([ObjectWithAttributes::class, 'method'], 'parameter'); - $attributes = new NativeAttributes($reflection); - - self::assertCount(1, $attributes); - self::assertTrue($attributes->has(BasicAttribute::class)); - self::assertCount(1, $attributes->ofType(BasicAttribute::class)); - } - - public function test_function_attributes_are_fetched_correctly(): void - { - $reflection = new ReflectionFunction( - #[BasicAttribute] - fn () => 'foo' - ); - $attributes = new NativeAttributes($reflection); - - self::assertCount(1, $attributes); - self::assertTrue($attributes->has(BasicAttribute::class)); - self::assertCount(1, $attributes->ofType(BasicAttribute::class)); - } -} diff --git a/tests/Unit/Definition/ParameterDefinitionTest.php b/tests/Unit/Definition/ParameterDefinitionTest.php index 35f54f69..2f51ff7c 100644 --- a/tests/Unit/Definition/ParameterDefinitionTest.php +++ b/tests/Unit/Definition/ParameterDefinitionTest.php @@ -4,8 +4,8 @@ namespace CuyZ\Valinor\Tests\Unit\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\ParameterDefinition; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; @@ -19,7 +19,7 @@ public function test_parameter_data_can_be_retrieved(): void $isOptional = true; $isVariadic = true; $defaultValue = 'Some parameter default value'; - $attributes = new FakeAttributes(); + $attributes = new Attributes(); $parameter = new ParameterDefinition( $name, diff --git a/tests/Unit/Definition/PropertyDefinitionTest.php b/tests/Unit/Definition/PropertyDefinitionTest.php index 4d505160..29b40268 100644 --- a/tests/Unit/Definition/PropertyDefinitionTest.php +++ b/tests/Unit/Definition/PropertyDefinitionTest.php @@ -4,8 +4,8 @@ namespace CuyZ\Valinor\Tests\Unit\Definition; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Definition\PropertyDefinition; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; @@ -19,7 +19,7 @@ public function test_property_data_can_be_retrieved(): void $hasDefaultValue = true; $defaultValue = 'Some property default value'; $isPublic = true; - $attributes = new FakeAttributes(); + $attributes = new Attributes(); $property = new PropertyDefinition( $name, diff --git a/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php b/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php index f5a3d613..6ff74256 100644 --- a/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php +++ b/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php @@ -10,7 +10,6 @@ use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch; use CuyZ\Valinor\Definition\Exception\UnknownTypeAliasImport; use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository; -use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeAttributesRepository; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory; use CuyZ\Valinor\Tests\Fixture\Object\AbstractObjectWithInterface; @@ -32,7 +31,6 @@ protected function setUp(): void $this->repository = new ReflectionClassDefinitionRepository( new FakeTypeParserFactory(), - new FakeAttributesRepository(), ); } diff --git a/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php b/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php index 0b29f024..e9bfb4c7 100644 --- a/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php +++ b/tests/Unit/Mapper/Tree/Builder/TreeNodeTest.php @@ -5,9 +5,9 @@ namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree\Builder; use AssertionError; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode; use CuyZ\Valinor\Mapper\Tree\Shell; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Builder\FakeTreeNode; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage; @@ -48,8 +48,8 @@ public function test_node_branch_values_can_be_retrieved(): void { $typeChildA = FakeType::permissive(); $typeChildB = FakeType::permissive(); - $attributesChildA = new FakeAttributes(); - $attributesChildB = new FakeAttributes(); + $attributesChildA = new Attributes(); + $attributesChildB = new Attributes(); $node = FakeTreeNode::branch([ 'foo' => ['type' => $typeChildA, 'value' => 'foo', 'attributes' => $attributesChildA], diff --git a/tests/Unit/Mapper/Tree/ShellTest.php b/tests/Unit/Mapper/Tree/ShellTest.php index 55d91e26..40571784 100644 --- a/tests/Unit/Mapper/Tree/ShellTest.php +++ b/tests/Unit/Mapper/Tree/ShellTest.php @@ -4,8 +4,8 @@ namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree; +use CuyZ\Valinor\Definition\Attributes; use CuyZ\Valinor\Mapper\Tree\Shell; -use CuyZ\Valinor\Tests\Fake\Definition\FakeAttributes; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use PHPUnit\Framework\TestCase; @@ -65,7 +65,7 @@ public function test_shell_child_values_can_be_retrieved(): void { $value = 'some value'; $type = FakeType::permissive(); - $attributes = new FakeAttributes(); + $attributes = new Attributes(); $shell = Shell::root(new FakeType(), []); $child = $shell->child('foo', $type, $attributes)->withValue($value);