From 6537a2db8148932ba2476af6c01fea5b55e99a74 Mon Sep 17 00:00:00 2001 From: Josha von Gizycki Date: Tue, 13 Oct 2026 17:05:28 +0200 Subject: [PATCH] first draft for child entities recursion check missing --- src/Analyze/EntityAnalyzer.php | 38 +++++++++---- src/Analyze/RepositoryAnalyzer.php | 6 +- src/Analyze/Result/EntityField.php | 6 +- src/Analyze/Result/Type/ArrayType.php | 18 ++++++ src/Analyze/Result/{ => Type}/ClassType.php | 2 +- .../Result/{ => Type}/PrimitiveType.php | 2 +- .../{ListResultOf.php => ListOf.php} | 4 +- tests/Analyze/EntityAnalyzerTest.php | 56 ++++++++++++++++++- .../Analyze/EntityWithListOfFullEntities.php | 18 ++++++ tests/Analyze/IdOnlyEntity.php | 15 +++++ .../Analyze/InvalidListResultOfRepository.php | 4 +- tests/Analyze/Repository.php | 4 +- tests/Sql/DataToEntityMapperTest.php | 2 +- 13 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 src/Analyze/Result/Type/ArrayType.php rename src/Analyze/Result/{ => Type}/ClassType.php (81%) rename src/Analyze/Result/{ => Type}/PrimitiveType.php (75%) rename src/Attribute/{ListResultOf.php => ListOf.php} (70%) create mode 100644 tests/Analyze/EntityWithListOfFullEntities.php create mode 100644 tests/Analyze/IdOnlyEntity.php diff --git a/src/Analyze/EntityAnalyzer.php b/src/Analyze/EntityAnalyzer.php index b834381..c902758 100644 --- a/src/Analyze/EntityAnalyzer.php +++ b/src/Analyze/EntityAnalyzer.php @@ -5,20 +5,19 @@ declare(strict_types=1); namespace Kartierung\Analyze; use Kartierung\Analyze\Exception\InvalidEntity; -use Kartierung\Analyze\Exception\InvalidRepository; -use Kartierung\Analyze\Result\ClassType; use Kartierung\Analyze\Result\EntityField; use Kartierung\Analyze\Result\EntityResult; use Kartierung\Analyze\Result\FieldAccess; -use Kartierung\Analyze\Result\PrimitiveType; +use Kartierung\Analyze\Result\Type\ArrayType; +use Kartierung\Analyze\Result\Type\ClassType; +use Kartierung\Analyze\Result\Type\PrimitiveType; use Kartierung\Attribute\Column; use Kartierung\Attribute\Id; +use Kartierung\Attribute\ListOf; use Kartierung\Attribute\Table; use ReflectionClass; -use ReflectionIntersectionType; use ReflectionNamedType; use ReflectionParameter; -use ReflectionUnionType; /** * @template E of object @@ -34,6 +33,7 @@ readonly class EntityAnalyzer /** * @return EntityResult + * @noinspection PhpDocMissingThrowsInspection */ public function analyze(): EntityResult { @@ -102,9 +102,6 @@ readonly class EntityAnalyzer return $idFields[array_key_first($idFields)]; } - /** - * @return EntityField - */ private function fieldFromParameter(ReflectionParameter $param): EntityField { $attrs = $param->getAttributes(Column::class); @@ -130,7 +127,7 @@ readonly class EntityAnalyzer ); } - private function parameterType(ReflectionParameter $param): PrimitiveType|ClassType + private function parameterType(ReflectionParameter $param): PrimitiveType|ClassType|ArrayType { if (!$param->hasType()) { throw new InvalidEntity( @@ -140,9 +137,9 @@ readonly class EntityAnalyzer $type = $param->getType(); if (!$type instanceof ReflectionNamedType) { - throw new InvalidRepository( + throw new InvalidEntity( "Entity parameter $this->entity::$param->name has an invalid return type. " - . 'Simple type expected. Unions, Intersections or no type are not simple.' + . "Simple type expected. Unions, Intersections or no type are not simple." ); } @@ -150,6 +147,23 @@ readonly class EntityAnalyzer /** @var class-string $classType */ $classType = $type->getName(); + if ($classType === "array") { + $listAttr = $param->getAttributes(ListOf::class); + if (empty($listAttr) || count($listAttr) > 1) { + throw new InvalidEntity( + "Entity parameter $this->entity::$param->name declares its typr as array. " + . "Exactly one #[ListOf(...)] attribute expected." + ); + } + + /** @var ListOf $listAttrInstance */ + $listAttrInstance = $listAttr[0]->newInstance(); + + return new ArrayType( + (new EntityAnalyzer($listAttrInstance->entitiyFqcn))->analyze() + ); + } + return match ($type->getName()) { "string" => PrimitiveType::STRING, "bool" => PrimitiveType::BOOLEAN, @@ -161,7 +175,7 @@ readonly class EntityAnalyzer /** * @param ReflectionClass $refClass - * @return FieldAccess + * @noinspection PhpDocMissingThrowsInspection */ private function readAccess(ReflectionClass $refClass, ReflectionParameter $param): FieldAccess { diff --git a/src/Analyze/RepositoryAnalyzer.php b/src/Analyze/RepositoryAnalyzer.php index da09e3e..03080f2 100644 --- a/src/Analyze/RepositoryAnalyzer.php +++ b/src/Analyze/RepositoryAnalyzer.php @@ -9,7 +9,7 @@ use Kartierung\Analyze\Result\ParameterFunction; use Kartierung\Analyze\Result\RepositoryMethod; use Kartierung\Analyze\Result\RepositoryMethodParameter; use Kartierung\Analyze\Result\RepositoryResult; -use Kartierung\Attribute\ListResultOf; +use Kartierung\Attribute\ListOf; use Kartierung\Attribute\Query; use Kartierung\Repository\ResolutionStrategy\ResolutionStrategyFactory; use ReflectionClass; @@ -126,11 +126,11 @@ readonly class RepositoryAnalyzer */ private function listType(ReflectionMethod $refMethod): ?string { - $listResultOfAttr = $refMethod->getAttributes(ListResultOf::class); + $listResultOfAttr = $refMethod->getAttributes(ListOf::class); $listType = null; if (count($listResultOfAttr) === 1) { - /** @var ListResultOf $inst */ + /** @var ListOf $inst */ $inst = $listResultOfAttr[0]->newInstance(); $listType = $inst->entitiyFqcn; diff --git a/src/Analyze/Result/EntityField.php b/src/Analyze/Result/EntityField.php index cfb6f0d..de61337 100644 --- a/src/Analyze/Result/EntityField.php +++ b/src/Analyze/Result/EntityField.php @@ -4,11 +4,15 @@ declare(strict_types=1); namespace Kartierung\Analyze\Result; +use Kartierung\Analyze\Result\Type\ArrayType; +use Kartierung\Analyze\Result\Type\ClassType; +use Kartierung\Analyze\Result\Type\PrimitiveType; + readonly class EntityField { public function __construct( public string $name, - public PrimitiveType|ClassType $type, + public PrimitiveType|ClassType|ArrayType $type, public string $columnName, public bool $isIdField, public FieldAccess $readAccess, diff --git a/src/Analyze/Result/Type/ArrayType.php b/src/Analyze/Result/Type/ArrayType.php new file mode 100644 index 0000000..c5ad9a6 --- /dev/null +++ b/src/Analyze/Result/Type/ArrayType.php @@ -0,0 +1,18 @@ + $entity + */ + public function __construct( + public EntityResult $entity + ) {} +} diff --git a/src/Analyze/Result/ClassType.php b/src/Analyze/Result/Type/ClassType.php similarity index 81% rename from src/Analyze/Result/ClassType.php rename to src/Analyze/Result/Type/ClassType.php index 90f6a5a..a84ebf3 100644 --- a/src/Analyze/Result/ClassType.php +++ b/src/Analyze/Result/Type/ClassType.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Kartierung\Analyze\Result; +namespace Kartierung\Analyze\Result\Type; readonly class ClassType { diff --git a/src/Analyze/Result/PrimitiveType.php b/src/Analyze/Result/Type/PrimitiveType.php similarity index 75% rename from src/Analyze/Result/PrimitiveType.php rename to src/Analyze/Result/Type/PrimitiveType.php index c815a70..1de52fc 100644 --- a/src/Analyze/Result/PrimitiveType.php +++ b/src/Analyze/Result/Type/PrimitiveType.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Kartierung\Analyze\Result; +namespace Kartierung\Analyze\Result\Type; enum PrimitiveType { diff --git a/src/Attribute/ListResultOf.php b/src/Attribute/ListOf.php similarity index 70% rename from src/Attribute/ListResultOf.php rename to src/Attribute/ListOf.php index b6b1301..dedf678 100644 --- a/src/Attribute/ListResultOf.php +++ b/src/Attribute/ListOf.php @@ -6,8 +6,8 @@ namespace Kartierung\Attribute; use Attribute; -#[Attribute(Attribute::TARGET_METHOD)] -readonly class ListResultOf +#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)] +readonly class ListOf { /** * @param class-string $entitiyFqcn diff --git a/tests/Analyze/EntityAnalyzerTest.php b/tests/Analyze/EntityAnalyzerTest.php index 073503b..0a2fa38 100644 --- a/tests/Analyze/EntityAnalyzerTest.php +++ b/tests/Analyze/EntityAnalyzerTest.php @@ -6,8 +6,10 @@ namespace Kartierung\Analyze; use Kartierung\Analyze\Exception\InvalidEntity; use Kartierung\Analyze\Result\EntityField; +use Kartierung\Analyze\Result\EntityResult; use Kartierung\Analyze\Result\FieldAccess; -use Kartierung\Analyze\Result\PrimitiveType; +use Kartierung\Analyze\Result\Type\ArrayType; +use Kartierung\Analyze\Result\Type\PrimitiveType; use Kartierung\FullEntity; use Kartierung\PropertyPropagatedEntity; use PHPUnit\Framework\TestCase; @@ -114,4 +116,56 @@ class EntityAnalyzerTest extends TestCase // Then $this->assertEquals("EntityWithoutTableAttribute", $result->tableName); } + + public function testArrayOfChildEntities(): void + { + // Given + $entity = EntityWithListOfFullEntities::class; + + // When + $result = (new EntityAnalyzer($entity))->analyze(); + + // Then + $this->assertEquals( + new EntityField( + name: 'id', + type: PrimitiveType::INTEGER, + columnName: 'id', + isIdField: true, + readAccess: FieldAccess::PUBLIC + ), + $result->fields[0] + ); + $this->assertEquals( + new EntityField( + name: 'children', + type: new ArrayType( + new EntityResult( + classFqcn: IdOnlyEntity::class, + tableName: 'IdOnlyEntity', + fields: [ + new EntityField( + name: 'id', + type: PrimitiveType::INTEGER, + columnName: 'id', + isIdField: true, + readAccess: FieldAccess::PUBLIC + ) + ], + idField: new EntityField( + name: 'id', + type: PrimitiveType::INTEGER, + columnName: 'id', + isIdField: true, + readAccess: FieldAccess::PUBLIC + ) + ) + ), + columnName: 'children', + isIdField: false, + readAccess: FieldAccess::PUBLIC + ), + $result->fields[1] + ); + } } diff --git a/tests/Analyze/EntityWithListOfFullEntities.php b/tests/Analyze/EntityWithListOfFullEntities.php new file mode 100644 index 0000000..440559f --- /dev/null +++ b/tests/Analyze/EntityWithListOfFullEntities.php @@ -0,0 +1,18 @@ +