Skip to content

Commit

Permalink
FindMethodCompiler generates SQL and passes it to SQLMethodCompiler
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubkulhan committed May 24, 2024
1 parent e411ae2 commit 16a7baf
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 106 deletions.
18 changes: 13 additions & 5 deletions data-access-kit/src/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@ public function get(object|string $objectOrClass, bool $requireTable = false): T
$className = is_object($objectOrClass) ? get_class($objectOrClass) : $objectOrClass;

if (isset($this->tablesByClassName[$className])) {
if ($requireTable && $this->tablesByClassName[$className]->name === null) {
throw static::missingTableException($className);
}
return $this->tablesByClassName[$className];
}

$rc = new ReflectionClass($className);
$tableRA = $rc->getAttributes(Table::class)[0] ?? null;
if ($tableRA === null) {
if ($requireTable) {
throw new LogicException(sprintf(
"Class %s is missing #[\\%s] attribute.",
$className,
Table::class,
));
throw static::missingTableException($className);
}

$table = new Table();
Expand Down Expand Up @@ -78,4 +77,13 @@ public function get(object|string $objectOrClass, bool $requireTable = false): T
return $this->tablesByClassName[$className] = $table;
}

private static function missingTableException(object|string $className): LogicException
{
return new LogicException(sprintf(
"Class %s is missing #[\\%s] attribute.",
$className,
Table::class,
));
}

}
4 changes: 2 additions & 2 deletions data-access-kit/src/Repository/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public function __construct(
Registry $registry,
)
{
$this->registerMethodCompiler(Find::class, new FindMethodCompiler($registry));
$this->registerMethodCompiler(SQL::class, $sqlMethodCompiler = new SQLMethodCompiler());
$this->registerMethodCompiler(Find::class, new FindMethodCompiler($registry, $sqlMethodCompiler));
$this->registerMethodCompiler(Count::class, new CountMethodCompiler());
$this->registerMethodCompiler(SQL::class, new SQLMethodCompiler());
$this->registerMethodCompiler(Delegate::class, new DelegateMethodCompiler());
}

Expand Down
104 changes: 40 additions & 64 deletions data-access-kit/src/Repository/Method/FindMethodCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\Repository\Attribute\Find;
use DataAccessKit\Repository\Attribute\SQL;
use DataAccessKit\Repository\Compiler;
use DataAccessKit\Repository\MethodCompilerInterface;
use DataAccessKit\Repository\Result;
Expand All @@ -25,82 +26,57 @@ class FindMethodCompiler implements MethodCompilerInterface

public function __construct(
private readonly Registry $registry,
private readonly SQLMethodCompiler $sqlMethodCompiler,
)
{
}

public function compile(Result $result, ResultMethod $method, $attribute): void
{
$constructor = $result->method("__construct");
$constructor->parameter("persistence")
->setVisibility("private readonly")
->setType($result->use(PersistenceInterface::class));
$constructor->parameter("registry")
->setVisibility("private readonly")
->setType($result->use(Registry::class));
$constructor->parameter("valueConverter")
->setVisibility("private readonly")
->setType($result->use(ValueConverterInterface::class));

$alias = $result->use($result->repository->class);

$table = $this->registry->get($result->repository->class, true);
$where = [];
$parameters = [];
foreach ($method->reflection->getParameters() as $parameter) {
$column = null;
foreach ($table->columns as $candidate) {
if ($candidate->reflection->getName() === $parameter->getName()) {
$column = $candidate;
break;
$select = $attribute->select;
if ($select === null) {
$select = implode(", ", array_map(fn($column) => $attribute->alias . "." . $column->name, $table->columns));
}
$from = $attribute->from ?? $table->name;
$where = $attribute->where;
if ($where === null) {
$conditions = [];
foreach ($method->reflection->getParameters() as $parameter) {
$column = null;
foreach ($table->columns as $candidate) {
if ($candidate->reflection->getName() === $parameter->getName()) {
$column = $candidate;
break;
}
}
if ($column === null) {
throw new LogicException(sprintf(
"Method [%s::%s] parameter [%s] does not match any property of [%s], and therefore cannot be used as a query condition.",
$result->reflection->getName(),
$method->reflection->getName(),
$parameter->getName(),
$result->repository->class,
));
}
}
if ($column === null) {
throw new LogicException(sprintf(
"Method [%s::%s] parameter [%s] does not match any property of [%s], and therefore cannot be used as a query condition.",
$result->reflection->getName(),
$method->reflection->getName(),
$parameter->getName(),
$result->repository->class,
));
}

$where[] = "t.{$column->name} = ?";
$parameters[] = "\$this->valueConverter->objectToDatabase(\$_table, \$_table->columns[" . Compiler::varExport($column->name) . "], \${$parameter->getName()})";
$conditions[] = "{$attribute->alias}.{$column->name} = @{$parameter->getName()}";
}
$where = implode(" AND ", $conditions);
}
$query = "SELECT " .
implode(", ", array_map(fn($column) => "t.{$column->name}", $table->columns)) .
" FROM {$table->name} t" .
" WHERE " . implode(" AND ", $where);

$method
->line("\$_table = \$this->registry->get({$alias}::class);")
->line("\$result = \$this->persistence->query({$alias}::class, " . Compiler::varExport($query) . ", [" . implode(", ", $parameters) . "]);");

$returnType = $method->reflection->getReturnType();
assert($returnType instanceof ReflectionNamedType);

if ($returnType->getName() === "iterable") {
$method->line("return \$result;");
} else if ($returnType->getName() === "array") {
$method->line("return iterator_to_array(\$result);");
} else if ($returnType->getName() === $result->repository->class) {
$method
->line("\$objects = iterator_to_array(\$result);")
->line("if (count(\$objects) === 0) {");
if ($returnType->allowsNull()) {
$method->indent()->line("return null;")->dedent();
} else {
$method->indent()->line("throw new \\RuntimeException(\"Entity not found.\");")->dedent();
}
$method
->line("} else if (count(\$objects) > 1) {")
->indent()->line("throw new \\RuntimeException(\"Multiple entities found.\");")->dedent()
->line("}")
->line("return \$objects[0];");
} else {
throw new LogicException("Unexpected return type: {$returnType->getName()}.");
$sql = "SELECT {$select} FROM {$from} {$attribute->alias} WHERE {$where}";
if ($attribute->orderBy !== null) {
$sql .= " ORDER BY {$attribute->orderBy}";
}
if ($attribute->limit !== null) {
$sql .= " LIMIT {$attribute->limit}";
}
if ($attribute->offset !== null) {
$sql .= " OFFSET {$attribute->offset}";
}

$this->sqlMethodCompiler->compile($result, $method, new SQL($sql));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
namespace DataAccessKit\Repository\Fixture;

use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\ValueConverterInterface;

final class FindArrayRepository implements FindArrayRepositoryInterface
{
public function __construct(
private readonly PersistenceInterface $persistence,
private readonly Registry $registry,
private readonly ValueConverterInterface $valueConverter,
)
{
}
Expand All @@ -20,8 +16,7 @@ final class FindArrayRepository implements FindArrayRepositoryInterface
string $title,
): array
{
$_table = $this->registry->get(Foo::class);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.title = ?', [$this->valueConverter->objectToDatabase($_table, $_table->columns['title'], $title)]);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.title = ?', [$title]);
return iterator_to_array($result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
namespace DataAccessKit\Repository\Fixture;

use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\ValueConverterInterface;

final class FindIterableRepository implements FindIterableRepositoryInterface
{
public function __construct(
private readonly PersistenceInterface $persistence,
private readonly Registry $registry,
private readonly ValueConverterInterface $valueConverter,
)
{
}
Expand All @@ -20,8 +16,7 @@ final class FindIterableRepository implements FindIterableRepositoryInterface
string $title,
): iterable
{
$_table = $this->registry->get(Foo::class);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.title = ?', [$this->valueConverter->objectToDatabase($_table, $_table->columns['title'], $title)]);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.title = ?', [$title]);
return $result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
namespace DataAccessKit\Repository\Fixture;

use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\ValueConverterInterface;

final class GetRepository implements GetRepositoryInterface
{
public function __construct(
private readonly PersistenceInterface $persistence,
private readonly Registry $registry,
private readonly ValueConverterInterface $valueConverter,
)
{
}
Expand All @@ -20,13 +16,12 @@ final class GetRepository implements GetRepositoryInterface
int $id,
): Foo
{
$_table = $this->registry->get(Foo::class);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$this->valueConverter->objectToDatabase($_table, $_table->columns['id'], $id)]);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$id]);
$objects = iterator_to_array($result);
if (count($objects) === 0) {
throw new \RuntimeException("Entity not found.");
throw new LogicException("Not found");
} else if (count($objects) > 1) {
throw new \RuntimeException("Multiple entities found.");
throw new LogicException("Multiple objects found");
}
return $objects[0];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
namespace DataAccessKit\Repository\Fixture;

use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\ValueConverterInterface;

final class NullableGetRepository implements NullableGetRepositoryInterface
{
public function __construct(
private readonly PersistenceInterface $persistence,
private readonly Registry $registry,
private readonly ValueConverterInterface $valueConverter,
)
{
}
Expand All @@ -20,13 +16,12 @@ final class NullableGetRepository implements NullableGetRepositoryInterface
int $id,
): ?Foo
{
$_table = $this->registry->get(Foo::class);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$this->valueConverter->objectToDatabase($_table, $_table->columns['id'], $id)]);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$id]);
$objects = iterator_to_array($result);
if (count($objects) === 0) {
return null;
} else if (count($objects) > 1) {
throw new \RuntimeException("Multiple entities found.");
throw new LogicException("Multiple objects found");
}
return $objects[0];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
namespace DataAccessKit\Repository\Fixture;

use DataAccessKit\PersistenceInterface;
use DataAccessKit\Registry;
use DataAccessKit\ValueConverterInterface;

final class PassMethodAttributesRepository implements PassMethodAttributesRepositoryInterface
{
public function __construct(
private readonly PersistenceInterface $persistence,
private readonly Registry $registry,
private readonly ValueConverterInterface $valueConverter,
)
{
}
Expand All @@ -21,13 +17,12 @@ final class PassMethodAttributesRepository implements PassMethodAttributesReposi
int $id,
): Foo
{
$_table = $this->registry->get(Foo::class);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$this->valueConverter->objectToDatabase($_table, $_table->columns['id'], $id)]);
$result = $this->persistence->query(Foo::class, 'SELECT t.id, t.title FROM foos t WHERE t.id = ?', [$id]);
$objects = iterator_to_array($result);
if (count($objects) === 0) {
throw new \RuntimeException("Entity not found.");
throw new LogicException("Not found");
} else if (count($objects) > 1) {
throw new \RuntimeException("Multiple entities found.");
throw new LogicException("Multiple objects found");
}
return $objects[0];
}
Expand Down

0 comments on commit 16a7baf

Please sign in to comment.