diff --git a/src/Scaffolding/TableImplementation.php b/src/Scaffolding/TableImplementation.php index 4533bee67ae0a9f9ec0d3f47abd27c82e51e34d6..5243912241ec8f751c96ca14fcfaff27ed968658 100644 --- a/src/Scaffolding/TableImplementation.php +++ b/src/Scaffolding/TableImplementation.php @@ -89,9 +89,6 @@ final class TableImplementation implements Capability ->setBody("return [\n".$columnsArrayTemplate."\n];", $columnsDefinitions); - // Column references - // todo add - use constants? Or references to Column class? - $classType->addMethod('find') ->setParameters([ (new Code\Parameter('primaryKey')) @@ -99,12 +96,9 @@ final class TableImplementation implements Capability ]) ->setReturnType($this->rowClass) ->setReturnNullable() - ->setBody( - '$row = $this->tableManager->find($this, $primaryKey);' . "\n" . - '\assert($row instanceof ? || $row === NULL);' . "\n" . - 'return $row;', - [new Code\Literal($namespace->simplifyName($this->rowClass))] - ); + ->addBody('$row = $this->tableManager->find($this, $primaryKey, required: false);') + ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('return $row;'); $namespace->addUse(RowNotFound::class); $classType->addMethod('get') @@ -114,13 +108,9 @@ final class TableImplementation implements Capability ]) ->setReturnType($this->rowClass) ->addComment('@throws RowNotFound') - ->setBody( - '$row = $this->find($primaryKey);' . "\n" . - 'if ($row === NULL) {' . "\n" . - ' throw new RowNotFound();' . "\n" . - '}' . "\n" . - 'return $row;' - ); + ->addBody('$row = $this->tableManager->find($this, $primaryKey, required: true);') + ->addBody('\assert($row instanceof ?);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('return $row;'); $namespace->addUse(OrderBy::class); $namespace->addUse(Paginator::class); @@ -166,10 +156,8 @@ final class TableImplementation implements Capability ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) ->addComment('@throws RowNotFound') ->setReturnType($this->rowClass) - ->addBody('[$row, $count] = $this->tableManager->findOneBy($this, $conditions);') - ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) - ->addBody('if ($row === null) { throw new RowNotFound(); }') - ->addBody('if ($count > 1) { throw new TooManyRowsFound(); }') + ->addBody('$row = $this->tableManager->findOneBy($this, $conditions, required: true, unique: true);') + ->addBody('\assert($row instanceof ?);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) ->addBody('return $row;'); $classType->addMethod('findOneBy') @@ -177,13 +165,12 @@ final class TableImplementation implements Capability (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), ]) ->addComment('@param Condition|Condition[] $conditions') - ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) + ->addComment('@return ' . $namespace->simplifyName($this->rowClass) . '|null') ->addComment('@throws RowNotFound') ->setReturnType($this->rowClass) ->setReturnNullable() - ->addBody('[$row, $count] = $this->tableManager->findOneBy($this, $conditions);') + ->addBody('$row = $this->tableManager->findOneBy($this, $conditions, required: false, unique: true);') ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) - ->addBody('if ($count > 1) { throw new TooManyRowsFound(); }') ->addBody('return $row;'); $classType->addMethod('getFirstBy') @@ -196,9 +183,8 @@ final class TableImplementation implements Capability ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) ->addComment('@throws RowNotFound') ->setReturnType($this->rowClass) - ->addBody('[$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false);') - ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) - ->addBody('if ($row === null) { throw new RowNotFound(); }') + ->addBody('$row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: true, unique: false);') + ->addBody('\assert($row instanceof ?);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) ->addBody('return $row;'); $classType->addMethod('findFirstBy') @@ -208,11 +194,10 @@ final class TableImplementation implements Capability ]) ->addComment('@param Condition|Condition[] $conditions') ->addComment('@param array<OrderBy|Expression<mixed>> $orderBy') - ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) - ->addComment('@throws RowNotFound') + ->addComment('@return ' . $namespace->simplifyName($this->rowClass) . '|null') ->setReturnType($this->rowClass) ->setReturnNullable() - ->addBody('[$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false);') + ->addBody('$row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: false, unique: false);') ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) ->addBody('return $row;'); diff --git a/src/TableManager.php b/src/TableManager.php index 4b6880e1657b52496d065af8ff7b302841df5c86..3938260d30546c4f5e61664f995c9ea8a6775ab7 100644 --- a/src/TableManager.php +++ b/src/TableManager.php @@ -54,17 +54,12 @@ final class TableManager * @template TableType of Table * @param TableType $table * @param PrimaryKey<TableType> $primaryKey - * @return null|Row + * @return ($required is true ? Row : Row|null) + * @throws RowNotFound */ - public function find(Table $table, PrimaryKey $primaryKey): ?Row + public function find(Table $table, PrimaryKey $primaryKey, bool $required = true): ?Row { - $rows = $this->findBy($table, $primaryKey->getCondition($table)); - if (\count($rows) === 1) { - $row = \reset($rows); - return $row; - } - \assert(\count($rows) === 0); - return NULL; + return $this->findOneBy($table, $primaryKey->getCondition($table), required: $required); } /** @@ -142,9 +137,10 @@ final class TableManager * @param TableType $table * @param Condition|Condition[] $conditions * @param array<OrderBy|Expression<mixed>> $orderBy - * @return array{Row|null, int} + * @return ($required is true ? Row : Row|null) + * @throws RowNotFound */ - public function findOneBy(Table $table, Condition|array $conditions, array $orderBy = [], bool $checkCount = true): array + public function findOneBy(Table $table, Condition|array $conditions, array $orderBy = [], bool $required = true, bool $unique = true): ?Row { $result = $this->connection->query( 'SELECT *', @@ -159,32 +155,30 @@ final class TableManager return $orderBy->toSql()->getValues(); }) : [['%sql', 'true::boolean']], - '%lmt', $checkCount ? 2 : 1, ); foreach ($table::getDatabaseColumns() as $column) { $result->setType($column->getName(), NULL); } - $dibiRows = $result->fetchAll(); + $dibiRow = $result->fetch(); + if ($dibiRow === null) { + return ! $required ? null : throw new RowNotFound(); + } - /** @var class-string<Row> $rowClass */ - $rowClass = $table::getRowClass(); - $modelRows = []; - foreach ($dibiRows as $dibiRow) { - \assert($dibiRow instanceof \Dibi\Row); - $modelRows[] = $rowClass::reconstitute( - mapWithKeys( - $dibiRow->toArray(), - static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->fromDatabase($value) : null, - ), - ); + if ($unique && $result->fetch() !== null) { + throw new TooManyRowsFound(); } - return [ - $modelRows[0] ?? null, - \count($modelRows), - ]; + /** @var class-string<Row> $rowClass */ + $rowClass = $table::getRowClass(); + \assert($dibiRow instanceof \Dibi\Row); + return $rowClass::reconstitute( + mapWithKeys( + $dibiRow->toArray(), + static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->fromDatabase($value) : null, + ), + ); } /** diff --git a/tests/Fixtures/ConfigTable.php b/tests/Fixtures/ConfigTable.php index 605dd7294e124c6fef22862846951d30b5f490dc..660d80fb18e6f422a1cb3586833f54e6f3196483 100644 --- a/tests/Fixtures/ConfigTable.php +++ b/tests/Fixtures/ConfigTable.php @@ -80,8 +80,8 @@ final class ConfigTable implements Table public function find(ConfigPrimaryKey $primaryKey): ?ConfigRow { - $row = $this->tableManager->find($this, $primaryKey); - \assert($row instanceof ConfigRow || $row === NULL); + $row = $this->tableManager->find($this, $primaryKey, required: false); + \assert($row instanceof ConfigRow || $row === null); return $row; } @@ -91,10 +91,8 @@ final class ConfigTable implements Table */ public function get(ConfigPrimaryKey $primaryKey): ConfigRow { - $row = $this->find($primaryKey); - if ($row === NULL) { - throw new RowNotFound(); - } + $row = $this->tableManager->find($this, $primaryKey, required: true); + \assert($row instanceof ConfigRow); return $row; } @@ -131,24 +129,21 @@ final class ConfigTable implements Table */ public function getOneBy(Condition|array $conditions): ConfigRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); - \assert($row instanceof ConfigRow || $row === null); - if ($row === null) { throw new RowNotFound(); } - if ($count > 1) { throw new TooManyRowsFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, required: true, unique: true); + \assert($row instanceof ConfigRow); return $row; } /** * @param Condition|Condition[] $conditions - * @return ConfigRow + * @return ConfigRow|null * @throws RowNotFound */ public function findOneBy(Condition|array $conditions): ?ConfigRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + $row = $this->tableManager->findOneBy($this, $conditions, required: false, unique: true); \assert($row instanceof ConfigRow || $row === null); - if ($count > 1) { throw new TooManyRowsFound(); } return $row; } @@ -161,9 +156,8 @@ final class ConfigTable implements Table */ public function getFirstBy(Condition|array $conditions, array $orderBy = []): ConfigRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); - \assert($row instanceof ConfigRow || $row === null); - if ($row === null) { throw new RowNotFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: true, unique: false); + \assert($row instanceof ConfigRow); return $row; } @@ -171,12 +165,11 @@ final class ConfigTable implements Table /** * @param Condition|Condition[] $conditions * @param array<OrderBy|Expression<mixed>> $orderBy - * @return ConfigRow - * @throws RowNotFound + * @return ConfigRow|null */ public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?ConfigRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: false, unique: false); \assert($row instanceof ConfigRow || $row === null); return $row; } diff --git a/tests/Fixtures/GeneratedTable.php b/tests/Fixtures/GeneratedTable.php index 5dbe6bfb297ac19707312dd063b7e8af7d1a13ab..ee0834412cbe41e9dc14300214620d23babe826a 100644 --- a/tests/Fixtures/GeneratedTable.php +++ b/tests/Fixtures/GeneratedTable.php @@ -80,8 +80,8 @@ final class GeneratedTable implements Table public function find(GeneratedPrimaryKey $primaryKey): ?GeneratedRow { - $row = $this->tableManager->find($this, $primaryKey); - \assert($row instanceof GeneratedRow || $row === NULL); + $row = $this->tableManager->find($this, $primaryKey, required: false); + \assert($row instanceof GeneratedRow || $row === null); return $row; } @@ -91,10 +91,8 @@ final class GeneratedTable implements Table */ public function get(GeneratedPrimaryKey $primaryKey): GeneratedRow { - $row = $this->find($primaryKey); - if ($row === NULL) { - throw new RowNotFound(); - } + $row = $this->tableManager->find($this, $primaryKey, required: true); + \assert($row instanceof GeneratedRow); return $row; } @@ -131,24 +129,21 @@ final class GeneratedTable implements Table */ public function getOneBy(Condition|array $conditions): GeneratedRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); - \assert($row instanceof GeneratedRow || $row === null); - if ($row === null) { throw new RowNotFound(); } - if ($count > 1) { throw new TooManyRowsFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, required: true, unique: true); + \assert($row instanceof GeneratedRow); return $row; } /** * @param Condition|Condition[] $conditions - * @return GeneratedRow + * @return GeneratedRow|null * @throws RowNotFound */ public function findOneBy(Condition|array $conditions): ?GeneratedRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + $row = $this->tableManager->findOneBy($this, $conditions, required: false, unique: true); \assert($row instanceof GeneratedRow || $row === null); - if ($count > 1) { throw new TooManyRowsFound(); } return $row; } @@ -161,9 +156,8 @@ final class GeneratedTable implements Table */ public function getFirstBy(Condition|array $conditions, array $orderBy = []): GeneratedRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); - \assert($row instanceof GeneratedRow || $row === null); - if ($row === null) { throw new RowNotFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: true, unique: false); + \assert($row instanceof GeneratedRow); return $row; } @@ -171,12 +165,11 @@ final class GeneratedTable implements Table /** * @param Condition|Condition[] $conditions * @param array<OrderBy|Expression<mixed>> $orderBy - * @return GeneratedRow - * @throws RowNotFound + * @return GeneratedRow|null */ public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?GeneratedRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: false, unique: false); \assert($row instanceof GeneratedRow || $row === null); return $row; } diff --git a/tests/Fixtures/PackagesTable.php b/tests/Fixtures/PackagesTable.php index 4f6ca4d3d040a6fac92e197fea54421128537cf5..cd53a918b8dfb1e11d046a186f8ede02cd926eb2 100644 --- a/tests/Fixtures/PackagesTable.php +++ b/tests/Fixtures/PackagesTable.php @@ -80,8 +80,8 @@ final class PackagesTable implements Table public function find(PackagePrimaryKey $primaryKey): ?PackageRow { - $row = $this->tableManager->find($this, $primaryKey); - \assert($row instanceof PackageRow || $row === NULL); + $row = $this->tableManager->find($this, $primaryKey, required: false); + \assert($row instanceof PackageRow || $row === null); return $row; } @@ -91,10 +91,8 @@ final class PackagesTable implements Table */ public function get(PackagePrimaryKey $primaryKey): PackageRow { - $row = $this->find($primaryKey); - if ($row === NULL) { - throw new RowNotFound(); - } + $row = $this->tableManager->find($this, $primaryKey, required: true); + \assert($row instanceof PackageRow); return $row; } @@ -131,24 +129,21 @@ final class PackagesTable implements Table */ public function getOneBy(Condition|array $conditions): PackageRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); - \assert($row instanceof PackageRow || $row === null); - if ($row === null) { throw new RowNotFound(); } - if ($count > 1) { throw new TooManyRowsFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, required: true, unique: true); + \assert($row instanceof PackageRow); return $row; } /** * @param Condition|Condition[] $conditions - * @return PackageRow + * @return PackageRow|null * @throws RowNotFound */ public function findOneBy(Condition|array $conditions): ?PackageRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + $row = $this->tableManager->findOneBy($this, $conditions, required: false, unique: true); \assert($row instanceof PackageRow || $row === null); - if ($count > 1) { throw new TooManyRowsFound(); } return $row; } @@ -161,9 +156,8 @@ final class PackagesTable implements Table */ public function getFirstBy(Condition|array $conditions, array $orderBy = []): PackageRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); - \assert($row instanceof PackageRow || $row === null); - if ($row === null) { throw new RowNotFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: true, unique: false); + \assert($row instanceof PackageRow); return $row; } @@ -171,12 +165,11 @@ final class PackagesTable implements Table /** * @param Condition|Condition[] $conditions * @param array<OrderBy|Expression<mixed>> $orderBy - * @return PackageRow - * @throws RowNotFound + * @return PackageRow|null */ public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?PackageRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: false, unique: false); \assert($row instanceof PackageRow || $row === null); return $row; } diff --git a/tests/Fixtures/TestsTable.php b/tests/Fixtures/TestsTable.php index 65f7f5f41b8cc99f1675dd0eadb43b3ab2de5adc..66a5e647a643ec51b2b5c8c7758b93557728e9cb 100644 --- a/tests/Fixtures/TestsTable.php +++ b/tests/Fixtures/TestsTable.php @@ -80,8 +80,8 @@ final class TestsTable implements Table public function find(TestPrimaryKey $primaryKey): ?TestRow { - $row = $this->tableManager->find($this, $primaryKey); - \assert($row instanceof TestRow || $row === NULL); + $row = $this->tableManager->find($this, $primaryKey, required: false); + \assert($row instanceof TestRow || $row === null); return $row; } @@ -91,10 +91,8 @@ final class TestsTable implements Table */ public function get(TestPrimaryKey $primaryKey): TestRow { - $row = $this->find($primaryKey); - if ($row === NULL) { - throw new RowNotFound(); - } + $row = $this->tableManager->find($this, $primaryKey, required: true); + \assert($row instanceof TestRow); return $row; } @@ -131,24 +129,21 @@ final class TestsTable implements Table */ public function getOneBy(Condition|array $conditions): TestRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); - \assert($row instanceof TestRow || $row === null); - if ($row === null) { throw new RowNotFound(); } - if ($count > 1) { throw new TooManyRowsFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, required: true, unique: true); + \assert($row instanceof TestRow); return $row; } /** * @param Condition|Condition[] $conditions - * @return TestRow + * @return TestRow|null * @throws RowNotFound */ public function findOneBy(Condition|array $conditions): ?TestRow { - [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + $row = $this->tableManager->findOneBy($this, $conditions, required: false, unique: true); \assert($row instanceof TestRow || $row === null); - if ($count > 1) { throw new TooManyRowsFound(); } return $row; } @@ -161,9 +156,8 @@ final class TestsTable implements Table */ public function getFirstBy(Condition|array $conditions, array $orderBy = []): TestRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); - \assert($row instanceof TestRow || $row === null); - if ($row === null) { throw new RowNotFound(); } + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: true, unique: false); + \assert($row instanceof TestRow); return $row; } @@ -171,12 +165,11 @@ final class TestsTable implements Table /** * @param Condition|Condition[] $conditions * @param array<OrderBy|Expression<mixed>> $orderBy - * @return TestRow - * @throws RowNotFound + * @return TestRow|null */ public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?TestRow { - [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + $row = $this->tableManager->findOneBy($this, $conditions, $orderBy, required: false, unique: false); \assert($row instanceof TestRow || $row === null); return $row; }