diff --git a/src/Analyze/EntityAnalyzer.php b/src/Analyze/EntityAnalyzer.php index ef312d3..22394d4 100644 --- a/src/Analyze/EntityAnalyzer.php +++ b/src/Analyze/EntityAnalyzer.php @@ -9,6 +9,7 @@ use Kartierung\Attribute\Id; use Kartierung\Attribute\Table; use ReflectionClass; use ReflectionNamedType; +use ReflectionParameter; use ReflectionProperty; /** @@ -49,7 +50,6 @@ readonly class EntityAnalyzer $attrs = $refClass->getAttributes(Table::class); if (count($attrs) !== 1) { - //throw new InvalidEntity('Attribute #[Table] not found on entity ' . $refClass->name); return $refClass->getShortName(); } @@ -63,9 +63,17 @@ readonly class EntityAnalyzer */ private function fields(ReflectionClass $refClass): array { - $props = $refClass->getProperties(); + $ctor = $refClass->getConstructor(); + if (!$ctor) { + throw new InvalidEntity( + "Entity $this->entity does not declare a constructor. Implement one accepting all of the " + . "entitie's values." + ); + } - return array_map(fn(ReflectionProperty $prop) => $this->fieldFromProperty($prop), $props); + $params = $ctor->getParameters(); + + return array_map(fn(ReflectionParameter $param) => $this->fieldFromParameter($param), $params); } /** @@ -77,93 +85,61 @@ readonly class EntityAnalyzer $idFields = array_filter($fields, static fn(EntityField $f) => $f->isIdField); $nrIdFields = count($idFields); - if ($nrIdFields === 0 || $nrIdFields > 1) { - throw new InvalidEntity("Invalid number of id fields found: $nrIdFields, expected: 1"); + if ($nrIdFields !== 1) { + throw new InvalidEntity( + "Invalid number of id fields on entity $this->entity found: $nrIdFields, expected: 1" + ); } return $idFields[array_key_first($idFields)]; } /** - * @param ReflectionProperty $prop * @return EntityField */ - private function fieldFromProperty(ReflectionProperty $prop): EntityField + private function fieldFromParameter(ReflectionParameter $param): EntityField { - $attrs = $prop->getAttributes(Column::class); + $attrs = $param->getAttributes(Column::class); if (count($attrs) === 1) { - /** @var string $name */ - $name = $attrs[0]->getArguments()[0]; + /** @var Column $column */ + $column = $attrs[0]->newInstance(); + $name = $column->name ?: $param->getName(); } else { - $name = $prop->getName(); + $name = $param->getName(); } - $refClass = $prop->getDeclaringClass(); - $writeAccess = $this->writeAccess($refClass, $prop); - $readAccess = $this->readAccess($refClass, $prop); + $refClass = $param->getDeclaringClass(); + $readAccess = $this->readAccess($refClass, $param); return new EntityField( - name: $prop->name, + name: $param->name, fqcn: $this->entity, columnName: $name, - isIdField: count($prop->getAttributes(Id::class)) > 0, - writeAccess: $writeAccess, + isIdField: count($param->getAttributes(Id::class)) > 0, readAccess: $readAccess ); } /** * @param ReflectionClass $refClass - * @param ReflectionProperty $prop * @return FieldAccess */ - public function writeAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess + public function readAccess(ReflectionClass $refClass, ReflectionParameter $param): FieldAccess { - $wither = FieldAccess::witherName($prop->getName()); - $setter = FieldAccess::setterName($prop->getName()); - - if ($refClass->hasMethod($setter)) { - $writeAccess = FieldAccess::GETSET; - } elseif ($refClass->hasMethod($wither)) { - $returnType = $refClass->getMethod($wither)->getReturnType(); - if (!$returnType instanceof ReflectionNamedType || $returnType->getName() !== 'self') { - throw new InvalidEntity( - "Field $refClass->name::$prop->name defines wither writing method with invalid return type, " . - "expecting `self`." - ); - } - - $writeAccess = FieldAccess::WITHER; - } elseif ($prop->isPublic() && !$prop->isReadOnly()) { - $writeAccess = FieldAccess::PUBLIC; - } else { - throw new InvalidEntity( - "Field $refClass->name::$prop->name has no valid writing method. " - . "Implement $setter, $wither or make the field public." - ); - } - - return $writeAccess; - } + $getter = 'get' . ucfirst($param->getName()); - /** - * @param ReflectionClass $refClass - * @param ReflectionProperty $prop - * @return FieldAccess - */ - public function readAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess - { - $getter = 'get' . ucfirst($prop->getName()); + $hasPublicProp = $refClass->hasProperty($param->getName()) + && $refClass->getProperty($param->getName())->isPublic(); if ($refClass->hasMethod($getter)) { - $readAccess = FieldAccess::GETSET; - } elseif ($prop->isPublic()) { + $readAccess = FieldAccess::GETTER; + } elseif ($hasPublicProp) { $readAccess = FieldAccess::PUBLIC; } else { throw new InvalidEntity( - "Field $refClass->name::$prop->name has no valid reading method. " - . "Implement $getter or make the field public." + "Constructor parameter of entity $this->entity '$param->name' has no valid reading method. " + . "Implement $getter() or make the field public." ); } diff --git a/src/Analyze/EntityField.php b/src/Analyze/EntityField.php index f44806e..5b036b7 100644 --- a/src/Analyze/EntityField.php +++ b/src/Analyze/EntityField.php @@ -17,7 +17,6 @@ readonly class EntityField public string $fqcn, public string $columnName, public bool $isIdField, - public FieldAccess $writeAccess, public FieldAccess $readAccess, ) {} } diff --git a/src/Analyze/FieldAccess.php b/src/Analyze/FieldAccess.php index f5a1624..0018893 100644 --- a/src/Analyze/FieldAccess.php +++ b/src/Analyze/FieldAccess.php @@ -6,17 +6,6 @@ namespace Kartierung\Analyze; enum FieldAccess { - case GETSET; + case GETTER; case PUBLIC; - case WITHER; - - public static function setterName(string $fieldName): string - { - return 'set' . ucfirst($fieldName); - } - - public static function witherName(string $fieldName): string - { - return 'with' . ucfirst($fieldName); - } } diff --git a/src/Attribute/Column.php b/src/Attribute/Column.php index f740686..14389ac 100644 --- a/src/Attribute/Column.php +++ b/src/Attribute/Column.php @@ -6,7 +6,7 @@ namespace Kartierung\Attribute; use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] readonly class Column { public function __construct( diff --git a/src/Sql/DataToEntityMapper.php b/src/Sql/DataToEntityMapper.php index 237cb61..b611ab1 100644 --- a/src/Sql/DataToEntityMapper.php +++ b/src/Sql/DataToEntityMapper.php @@ -26,21 +26,15 @@ readonly class DataToEntityMapper */ public function toEntity(array $row): object { - /** @var E $entity */ - $entity = new $this->entityResult->classFqcn(); - + $params = []; foreach ($this->entityResult->fields as $field) { /** @var object $dbValue */ $dbValue = $row[$field->columnName]; - - match ($field->writeAccess) { - FieldAccess::GETSET => $this->getsetWrite($entity, $field, $dbValue), - FieldAccess::PUBLIC => $this->publicWrite($entity, $field, $dbValue), - FieldAccess::WITHER => $entity = $this->witherWrite($entity, $field, $dbValue), - }; + $params[] = $dbValue; } - return $entity; + /** @var E */ + return new $this->entityResult->classFqcn(...$params); } /** diff --git a/tests/Analyze/EntityAnalyzerTest.php b/tests/Analyze/EntityAnalyzerTest.php index be9de6e..3023c29 100644 --- a/tests/Analyze/EntityAnalyzerTest.php +++ b/tests/Analyze/EntityAnalyzerTest.php @@ -5,8 +5,10 @@ declare(strict_types=1); namespace Kartierung\Analyze; use Kartierung\EntityWithoutId; +use Kartierung\EntityWithoutTableAttribute; use Kartierung\EntityWithTwoIds; use Kartierung\FullEntity; +use Kartierung\PropertyPropagatedEntity; use PHPUnit\Framework\TestCase; class EntityAnalyzerTest extends TestCase @@ -33,25 +35,13 @@ class EntityAnalyzerTest extends TestCase $result = (new EntityAnalyzer($entity))->analyze(); // Then - $this->assertContainsEquals( - new EntityField( - name: 'stringField', - fqcn: $entity, - columnName: 'stringField', - isIdField: false, - writeAccess: FieldAccess::WITHER, - readAccess: FieldAccess::PUBLIC - ), - $result->fields - ); $this->assertContainsEquals( new EntityField( name: 'intField', fqcn: $entity, columnName: 'int-field', isIdField: false, - writeAccess: FieldAccess::PUBLIC, - readAccess: FieldAccess::GETSET + readAccess: FieldAccess::GETTER ), $result->fields ); @@ -61,7 +51,6 @@ class EntityAnalyzerTest extends TestCase fqcn: $entity, columnName: 'renamed-id-field', isIdField: true, - writeAccess: FieldAccess::GETSET, readAccess: FieldAccess::PUBLIC ), $result->fields @@ -83,7 +72,6 @@ class EntityAnalyzerTest extends TestCase fqcn: $entity, columnName: 'renamed-id-field', isIdField: true, - writeAccess: FieldAccess::GETSET, readAccess: FieldAccess::PUBLIC ), $result->idField @@ -113,4 +101,16 @@ class EntityAnalyzerTest extends TestCase // When (new EntityAnalyzer($entity))->analyze(); } + + public function testTableAttributeIsOptional(): void + { + // Given + $entity = EntityWithoutTableAttribute::class; + + // When + $result = (new EntityAnalyzer($entity))->analyze(); + + // Then + $this->assertEquals("EntityWithoutTableAttribute", $result->tableName); + } } diff --git a/tests/EntityWithoutTableAttribute.php b/tests/EntityWithoutTableAttribute.php new file mode 100644 index 0000000..aef2b16 --- /dev/null +++ b/tests/EntityWithoutTableAttribute.php @@ -0,0 +1,15 @@ +intField; } - - public function withStringField(string $stringField): self - { - $this->stringField = $stringField; - return $this; - } } diff --git a/tests/Repository/ResolutionStrategy/SimpleEntity.php b/tests/Repository/ResolutionStrategy/SimpleEntity.php index 7a3a015..3f8f647 100644 --- a/tests/Repository/ResolutionStrategy/SimpleEntity.php +++ b/tests/Repository/ResolutionStrategy/SimpleEntity.php @@ -8,10 +8,11 @@ use Kartierung\Attribute\Id; class SimpleEntity { - public string $stringcol; - - #[Id] - public int $intcol; + public function __construct( + #[Id] + public int $intcol, + public string $stringcol + ) {} public function withStringCol(string $value): self { diff --git a/tests/Sql/DateToEntityMapperTest.php b/tests/Sql/DateToEntityMapperTest.php index fce000f..0d407a8 100644 --- a/tests/Sql/DateToEntityMapperTest.php +++ b/tests/Sql/DateToEntityMapperTest.php @@ -16,23 +16,14 @@ class DateToEntityMapperTest extends TestCase // Given $mapper = new DataToEntityMapper( entityResult: new EntityResult( - classFqcn: EntityWithThreeAccessMethods::class, + classFqcn: EntityWithAllReadMechanics::class, tableName: '', fields: [ - new EntityField( - name: 'witherVal', - fqcn: 'int', - columnName: 'wither_val', - isIdField: false, - writeAccess: FieldAccess::WITHER, - readAccess: FieldAccess::WITHER - ), new EntityField( name: 'publicVal', fqcn: 'int', columnName: 'public_val', isIdField: false, - writeAccess: FieldAccess::PUBLIC, readAccess: FieldAccess::PUBLIC ), new EntityField( @@ -40,8 +31,7 @@ class DateToEntityMapperTest extends TestCase fqcn: 'int', columnName: 'setter_val', isIdField: false, - writeAccess: FieldAccess::GETSET, - readAccess: FieldAccess::GETSET + readAccess: FieldAccess::GETTER ) ], idField: new EntityField( @@ -49,14 +39,12 @@ class DateToEntityMapperTest extends TestCase fqcn: '', columnName: '', isIdField: false, - writeAccess: FieldAccess::GETSET, - readAccess: FieldAccess::GETSET + readAccess: FieldAccess::GETTER ) ) ); $row = [ - 'wither_val' => 1, 'public_val' => 2, 'setter_val' => 3 ]; @@ -65,8 +53,7 @@ class DateToEntityMapperTest extends TestCase $entity = $mapper->toEntity($row); // Then - $this->assertInstanceOf(EntityWithThreeAccessMethods::class, $entity); - $this->assertEquals(1, $entity->getWitherVal()); + $this->assertInstanceOf(EntityWithAllReadMechanics::class, $entity); $this->assertEquals(2, $entity->publicVal); $this->assertEquals(3, $entity->getSetterVal()); } diff --git a/tests/Sql/EntityWithAllReadMechanics.php b/tests/Sql/EntityWithAllReadMechanics.php new file mode 100644 index 0000000..7d10f6c --- /dev/null +++ b/tests/Sql/EntityWithAllReadMechanics.php @@ -0,0 +1,18 @@ +setterVal; + } +} diff --git a/tests/Sql/EntityWithThreeAccessMethods.php b/tests/Sql/EntityWithThreeAccessMethods.php deleted file mode 100644 index 0201442..0000000 --- a/tests/Sql/EntityWithThreeAccessMethods.php +++ /dev/null @@ -1,35 +0,0 @@ -setterVal; - } - - public function setSetterVal(int $value): void - { - $this->setterVal = $value; - } - - public function withWitherVal(int $value): self - { - $this->witherVal = $value; - return $this; - } - - public function getWitherVal(): int - { - return $this->witherVal; - } -}