From 7783b245df93a8c1bfc38247719acb626e7a8d03 Mon Sep 17 00:00:00 2001 From: Josha von Gizycki Date: Mon, 19 Oct 2026 17:07:28 +0200 Subject: [PATCH] implement FindById, move to proper type alanysis on repository return types --- src/Analyze/RepositoryAnalyzer.php | 28 +++-- src/Analyze/Result/RepositoryMethod.php | 7 +- .../ResolutionStrategy/FindById.php | 38 ++++++- .../ResolutionStrategyFactory.php | 4 +- tests/Analyze/RepositoryAnalyzerTest.php | 4 +- .../ResolutionStrategy/FindAllTest.php | 1 + .../ResolutionStrategy/FindByIdTest.php | 101 ++++++++++++++++++ .../ResolutionStrategyFactoryTest.php | 25 +++-- .../ResolutionStrategy/SimpleEntity.php | 8 -- 9 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 tests/Repository/ResolutionStrategy/FindByIdTest.php diff --git a/src/Analyze/RepositoryAnalyzer.php b/src/Analyze/RepositoryAnalyzer.php index 03080f2..a0b2119 100644 --- a/src/Analyze/RepositoryAnalyzer.php +++ b/src/Analyze/RepositoryAnalyzer.php @@ -15,6 +15,7 @@ use Kartierung\Repository\ResolutionStrategy\ResolutionStrategyFactory; use ReflectionClass; use ReflectionIntersectionType; use ReflectionMethod; +use ReflectionNamedType; use ReflectionUnionType; /** @@ -54,23 +55,25 @@ readonly class RepositoryAnalyzer $methods = []; foreach ($refMethods as $refMethod) { $query = $this->queryString($refMethod); - $returnType = $this->returnType($refMethod); + list($returnType, $returnsNullable) = $this->returnType($refMethod); $listType = $this->listType($refMethod); $params = $this->parameters($refMethod); $name = $refMethod->name; $resolutionStrategy = $this->methodValidator->forRepositoryMethod( - $refClass->name, - $name, - $query, - $returnType, - $listType, - $params + repName: $refClass->name, + methodName: $name, + query: $query, + returnType: $returnType, + returnsNullable: $returnsNullable, + listType: $listType, + params: $params ); $methods[$refMethod->name] = new RepositoryMethod( query: $query, returnType: $returnType, + returnsNullable: $returnsNullable, returnListType: $listType, name: $name, parameters: $params, @@ -101,9 +104,9 @@ readonly class RepositoryAnalyzer /** * @param ReflectionMethod $refMethod - * @return class-string|string + * @return array{class-string|string, bool} */ - private function returnType(ReflectionMethod $refMethod): string + private function returnType(ReflectionMethod $refMethod): array { $returnType = $refMethod->getReturnType(); @@ -116,8 +119,11 @@ readonly class RepositoryAnalyzer ); } - /** @var class-string|string */ - return $returnType->__toString(); + /** @var ReflectionNamedType $returnType */ + + /** @var class-string|string $typeStr */ + $typeStr = $returnType->getName(); + return [$typeStr, $returnType->allowsNull()]; } /** diff --git a/src/Analyze/Result/RepositoryMethod.php b/src/Analyze/Result/RepositoryMethod.php index 3f58111..abb6b3f 100644 --- a/src/Analyze/Result/RepositoryMethod.php +++ b/src/Analyze/Result/RepositoryMethod.php @@ -6,6 +6,7 @@ namespace Kartierung\Analyze\Result; use Kartierung\Repository\ResolutionStrategy\ResolutionStrategy; use Kartierung\Sql\LazyQuery; +use phpDocumentor\Reflection\Types\Boolean; readonly class RepositoryMethod { @@ -16,17 +17,13 @@ readonly class RepositoryMethod public function __construct( public ?string $query, public string $returnType, + public bool $returnsNullable, public ?string $returnListType, public string $name, public array $parameters, public ResolutionStrategy $resolutionStrategy ) {} - public function returnsNullable(): bool - { - return str_starts_with($this->returnType, '?'); - } - /** * @param array $parameters */ diff --git a/src/Repository/ResolutionStrategy/FindById.php b/src/Repository/ResolutionStrategy/FindById.php index 14529cb..317966b 100644 --- a/src/Repository/ResolutionStrategy/FindById.php +++ b/src/Repository/ResolutionStrategy/FindById.php @@ -4,17 +4,51 @@ declare(strict_types=1); namespace Kartierung\Repository\ResolutionStrategy; +use Kartierung\Analyze\EntityAnalyzer; +use Kartierung\Analyze\Result\EntityField; use Kartierung\Analyze\Result\RepositoryMethod; +use Kartierung\Sql\DataToEntityMapper; use Kartierung\Sql\LazyQuery; use PDO; +use PDOStatement; -class FindById implements ResolutionStrategy +readonly class FindById implements ResolutionStrategy { public function createQuery(RepositoryMethod $method, array $parameters): LazyQuery { return new LazyQuery( - function (PDO $pdo) { + static function (PDO $pdo) use ($method, $parameters) { + /** @var class-string $type */ + $type = $method->returnType; + $entityResult = (new EntityAnalyzer($type))->analyze(); + $mapper = new DataToEntityMapper($entityResult); + $fields = implode( + ', ', + array_map( + static fn(EntityField $e) => $e->columnName, $entityResult->fields + ) + ); + + $idField = $entityResult->idField->name; + + $sql = " + SELECT $fields + FROM {$entityResult->tableName} + WHERE $idField = ? + "; + + /** @var PDOStatement $stmt */ + $stmt = $pdo->prepare($sql); + $stmt->execute([array_first($parameters)]); + + /** @var array|false $row */ + $row = $stmt->fetch(mode: PDO::FETCH_ASSOC); + if($row) { + return $mapper->toEntity($row); + } + + return null; } ); } diff --git a/src/Repository/ResolutionStrategy/ResolutionStrategyFactory.php b/src/Repository/ResolutionStrategy/ResolutionStrategyFactory.php index acaf5e1..79ef2c4 100644 --- a/src/Repository/ResolutionStrategy/ResolutionStrategyFactory.php +++ b/src/Repository/ResolutionStrategy/ResolutionStrategyFactory.php @@ -17,12 +17,12 @@ class ResolutionStrategyFactory string $methodName, ?string $query, string $returnType, + bool $returnsNullable, ?string $listType, array $params ): ResolutionStrategy { $oneParam = count($params) === 1; - $returnsNullable = str_starts_with($returnType, '?'); return match (true) { $query !== null => new UserDefinedQuery(), @@ -40,7 +40,7 @@ class ResolutionStrategyFactory $methodName === 'findById' && $oneParam && $returnsNullable => new FindById(), $methodName === 'findById' => throw new InvalidRepository( - "fnyById functions expect exactly one parameter and nullable return type." . + "fnyById functions expect exactly one parameter and nullable return type. " . "Invalid function found in $repName" ), diff --git a/tests/Analyze/RepositoryAnalyzerTest.php b/tests/Analyze/RepositoryAnalyzerTest.php index 467df89..8629781 100644 --- a/tests/Analyze/RepositoryAnalyzerTest.php +++ b/tests/Analyze/RepositoryAnalyzerTest.php @@ -130,7 +130,7 @@ class RepositoryAnalyzerTest extends TestCase // Then $method = $result->methods['findById']; - $this->assertEquals('?Kartierung\FullEntity', $method->returnType); - $this->assertTrue($method->returnsNullable()); + $this->assertEquals('Kartierung\FullEntity', $method->returnType); + $this->assertTrue($method->returnsNullable); } } diff --git a/tests/Repository/ResolutionStrategy/FindAllTest.php b/tests/Repository/ResolutionStrategy/FindAllTest.php index 6c2d4ab..6e00170 100644 --- a/tests/Repository/ResolutionStrategy/FindAllTest.php +++ b/tests/Repository/ResolutionStrategy/FindAllTest.php @@ -34,6 +34,7 @@ class FindAllTest extends SqliteBaseCase $method = new RepositoryMethod( query: '', returnType: '', + returnsNullable: true, returnListType: SimpleEntity::class, name: '', parameters: [], diff --git a/tests/Repository/ResolutionStrategy/FindByIdTest.php b/tests/Repository/ResolutionStrategy/FindByIdTest.php new file mode 100644 index 0000000..5033ee1 --- /dev/null +++ b/tests/Repository/ResolutionStrategy/FindByIdTest.php @@ -0,0 +1,101 @@ +getPdo()->exec( + " + CREATE TABLE SimpleEntity ( + stringcol VARCHAR(250), + intcol INT + ); + INSERT INTO SimpleEntity(stringcol, intcol) + VALUES ('dings', 1), + ('bumms', 2), + ('foobar', 3); + " + ); + } + + #[Group('integration')] + public function testSelectsFirstEntry(): void + { + // Given + $method = new RepositoryMethod( + query: '', + returnType: SimpleEntity::class, + returnsNullable: true, + returnListType: '', + name: '', + parameters: [], + resolutionStrategy: new FindById() + ); + $query = $method->createQuery([1]); + + // When + $result = $query->execute($this->getPdo()); + + // Then + $this->assertEquals( + new SimpleEntity(1, 'dings'), + $result + ); + } + + #[Group('integration')] + public function testSelectsThirdEntry(): void + { + // Given + $method = new RepositoryMethod( + query: '', + returnType: SimpleEntity::class, + returnsNullable: true, + returnListType: '', + name: '', + parameters: [], + resolutionStrategy: new FindById() + ); + $query = $method->createQuery([3]); + + // When + $result = $query->execute($this->getPdo()); + + // Then + $this->assertEquals( + new SimpleEntity(3, 'foobar'), + $result + ); + } + + #[Group('integration')] + public function testSelectsNothingMatching(): void + { + // Given + $method = new RepositoryMethod( + query: '', + returnType: SimpleEntity::class, + returnsNullable: true, + returnListType: '', + name: '', + parameters: [], + resolutionStrategy: new FindById() + ); + $query = $method->createQuery([4]); + + // When + $result = $query->execute($this->getPdo()); + + // Then + $this->assertNull($result); + } +} diff --git a/tests/Repository/ResolutionStrategy/ResolutionStrategyFactoryTest.php b/tests/Repository/ResolutionStrategy/ResolutionStrategyFactoryTest.php index a3a9856..1110809 100644 --- a/tests/Repository/ResolutionStrategy/ResolutionStrategyFactoryTest.php +++ b/tests/Repository/ResolutionStrategy/ResolutionStrategyFactoryTest.php @@ -13,11 +13,11 @@ class ResolutionStrategyFactoryTest extends TestCase public static function validFunctionsProvider(): array { return [ - ['R', 'save', null, '', null, [1], SaveEntity::class], - ['R', 'delete', null, 'int', null, [1], DeleteEntity::class], - ['R', 'findById', null, '?int', null, [1], FindById::class], - ['R', 'findAll', null, 'array', FullEntity::class, [], FindAll::class], - ['R', 'something', 'SELECT', 'int', null, [], UserDefinedQuery::class] + ['R', 'save', null, '', false, null, [1], SaveEntity::class], + ['R', 'delete', null, 'int', false, null, [1], DeleteEntity::class], + ['R', 'findById', null, 'int', true, null, [1], FindById::class], + ['R', 'findAll', null, 'array', false, FullEntity::class, [], FindAll::class], + ['R', 'something', 'SELECT', 'int', false, null, [], UserDefinedQuery::class] ]; } @@ -27,6 +27,7 @@ class ResolutionStrategyFactoryTest extends TestCase string $name, ?string $query, string $returnType, + bool $returnsNullable, ?string $listType, array $params, string $strategyClass @@ -38,6 +39,7 @@ class ResolutionStrategyFactoryTest extends TestCase methodName: $name, query: $query, returnType: $returnType, + returnsNullable: $returnsNullable, listType: $listType, params: $params ); @@ -69,12 +71,13 @@ class ResolutionStrategyFactoryTest extends TestCase { // When $function = (new ResolutionStrategyFactory())->forRepositoryMethod( - $repName, - $name, - $query, - $returnType, - $listType, - $params + repName: $repName, + methodName: $name, + query: $query, + returnType: $returnType, + returnsNullable: false, + listType: $listType, + params: $params ); // Then diff --git a/tests/Repository/ResolutionStrategy/SimpleEntity.php b/tests/Repository/ResolutionStrategy/SimpleEntity.php index 3f8f647..a2e28c4 100644 --- a/tests/Repository/ResolutionStrategy/SimpleEntity.php +++ b/tests/Repository/ResolutionStrategy/SimpleEntity.php @@ -13,12 +13,4 @@ class SimpleEntity public int $intcol, public string $stringcol ) {} - - public function withStringCol(string $value): self - { - $new = new self(); - $new->stringcol = $value; - return $new; - } - }