Skip to content
Snippets Groups Projects
Verified Commit 1322df29 authored by Jiří Pudil's avatar Jiří Pudil
Browse files

prototype for a more robust creation/modification API

parent 149a7104
No related branches found
No related tags found
1 merge request!50more robust creation/modification API
<?php
declare(strict_types=1);
namespace Grifart\Tables;
final class DefaultValue
{
}
......@@ -8,11 +8,13 @@ use Grifart\ClassScaffolder\Capabilities\Capability;
use Grifart\ClassScaffolder\ClassInNamespace;
use Grifart\ClassScaffolder\Definition\ClassDefinition;
use Grifart\ClassScaffolder\Definition\Types\Type as PhpType;
use Grifart\ClassScaffolder\Definition\Types\UnionType;
use Grifart\Tables\CaseConversion;
use Grifart\Tables\Column;
use Grifart\Tables\ColumnMetadata;
use Grifart\Tables\ColumnNotFound;
use Grifart\Tables\Conditions\Condition;
use Grifart\Tables\DefaultValue;
use Grifart\Tables\Expression;
use Grifart\Tables\RowNotFound;
use Grifart\Tables\OrderBy\OrderBy;
......@@ -23,12 +25,16 @@ use Grifart\Tables\Type;
use Grifart\Tables\TypeResolver;
use Nette\PhpGenerator as Code;
use Nette\Utils\Paginator;
use function Functional\map;
use function Grifart\ClassScaffolder\Definition\Types\resolve;
use function usort;
final class TableImplementation implements Capability
{
/**
* @param array<string, ColumnMetadata> $columnMetadata
* @param array<string, PhpType> $columnPhpTypes
* @param array<string, PhpType> $primaryKeyFields
*/
public function __construct(
private string $schema,
......@@ -38,6 +44,7 @@ final class TableImplementation implements Capability
private string $modificationClass,
private array $columnMetadata,
private array $columnPhpTypes,
private array $primaryKeyFields,
) {}
public function applyTo(
......@@ -63,7 +70,7 @@ final class TableImplementation implements Capability
// column info:
$namespace->addUse(ColumnMetadata::class);
$columnsDefinitions = []; // name => PhpLiteral
$columnsDefinitions = []; // name => Literal
$columnsArrayTemplate = [];
foreach($this->columnMetadata as $column) {
$columnsArrayTemplate[] = "\t? => new ColumnMetadata(?, ?, ?, ?)";
......@@ -96,7 +103,7 @@ final class TableImplementation implements Capability
'$row = $this->tableManager->find($this, $primaryKey);' . "\n" .
'\assert($row instanceof ? || $row === NULL);' . "\n" .
'return $row;',
[new Code\PhpLiteral($namespace->simplifyName($this->rowClass))]
[new Code\Literal($namespace->simplifyName($this->rowClass))]
);
$namespace->addUse(RowNotFound::class);
......@@ -129,7 +136,7 @@ final class TableImplementation implements Capability
"/** @var ?[] \$result */\n" .
"\$result = \$this->tableManager->getAll(\$this, \$orderBy, \$paginator);\n" .
'return $result;',
[new Code\PhpLiteral($namespace->simplifyName($this->rowClass))],
[new Code\Literal($namespace->simplifyName($this->rowClass))],
);
$namespace->addUse(Condition::class);
......@@ -145,7 +152,7 @@ final class TableImplementation implements Capability
->addComment('@param array<OrderBy|Expression<mixed>> $orderBy')
->addComment('@return ' . $namespace->simplifyName($this->rowClass) . '[]')
->setReturnType('array')
->addBody('/** @var ?[] $result */', [new Code\PhpLiteral($namespace->simplifyName($this->rowClass))])
->addBody('/** @var ?[] $result */', [new Code\Literal($namespace->simplifyName($this->rowClass))])
->addBody('$result = $this->tableManager->findBy($this, $conditions, $orderBy, $paginator);')
->addBody('return $result;');
......@@ -165,59 +172,88 @@ final class TableImplementation implements Capability
->addBody('return $result[0];');
$classType->addMethod('newEmpty')
->setReturnType($this->modificationClass)
->setBody(
'return ?::new();',
[new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))],
);
$newMethod = $classType->addMethod('new')
->setReturnType($this->modificationClass)
->setReturnType($this->rowClass)
->addBody('$primaryKey = ?::from(...?:);', [
new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
map(
$this->primaryKeyFields,
static fn($_, string $name) => new Code\Literal('$' . $name),
),
])
->addBody(
'$modifications = ?::new();',
[new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))],
[new Code\Literal($namespace->simplifyName($this->modificationClass))],
);
foreach ($this->columnMetadata as $columnMetadata) {
if ( ! $columnMetadata->hasDefaultValue()) {
$editMethod = $classType->addMethod('edit')
->setReturnType($this->rowClass)
->setParameters([
(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass),
])
->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
])
->addBody('$modifications = ?::update($primaryKey);', [new Code\Literal($namespace->simplifyName($this->modificationClass))]);
$columns = $this->columnMetadata;
usort($columns, fn (ColumnMetadata $a, ColumnMetadata $b) => $a->hasDefaultValue() <=> $b->hasDefaultValue());
foreach ([$newMethod, $editMethod] as $method) {
foreach ($columns as $columnMetadata) {
$hasDefaultValue = $columnMetadata->hasDefaultValue() || $method === $editMethod;
$fieldName = $columnMetadata->getName();
$fieldType = $this->columnPhpTypes[$fieldName];
$isNullable = $fieldType->isNullable();
if ($hasDefaultValue) {
$namespace->addUse(DefaultValue::class);
$fieldType = new UnionType($fieldType, resolve(DefaultValue::class));
}
$newMethod->addParameter($fieldName)
$parameter = $method->addParameter($fieldName)
->setType($fieldType->getTypeHint())
->setNullable($fieldType->isNullable());
->setNullable($isNullable);
if ($hasDefaultValue) {
$parameter->setDefaultValue(new Code\Literal('new ?', [new Code\Literal($namespace->simplifyName(DefaultValue::class))]));
}
if ($fieldType->requiresDocComment()) {
$newMethod->addComment(\sprintf(
$method->addComment(\sprintf(
'@param %s $%s',
$fieldType->getDocCommentType($namespace),
$fieldName,
));
}
$newMethod->addBody(
'$modifications->modify' . \ucfirst($fieldName) . '(?);',
[new Code\PhpLiteral('$' . $fieldName)],
if ($hasDefaultValue) {
$method->addBody(
'if (!? instanceof ?) {',
[new Code\Literal('$' . $fieldName), new Code\Literal($namespace->simplifyName(DefaultValue::class))],
);
}
$method->addBody(
($hasDefaultValue ? "\t" : '') . '$modifications->modify' . \ucfirst($fieldName) . '(?);',
[new Code\Literal('$' . $fieldName)],
);
}
}
$newMethod->addBody('return $modifications;');
if ($hasDefaultValue) {
$method->addBody('}');
}
}
$method->addBody('$this->save($modifications);');
$method->addBody('return $this->get($primaryKey);');
}
$classType->addMethod('edit')
->setReturnType($this->modificationClass)
->setParameters([
(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass),
])
->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
])
->addBody('return ?::update($primaryKey);', [new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))]);
$classType->addMethod('save')
->setPrivate()
->setReturnType('void')
->setParameters([
(new Code\Parameter('changes'))->setType($this->modificationClass)
......@@ -232,8 +268,8 @@ final class TableImplementation implements Capability
(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass)
])
->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
])
->addBody('$this->tableManager->delete($this, $primaryKey);');
......@@ -273,7 +309,7 @@ final class TableImplementation implements Capability
$constructor->addBody(\sprintf('/** @var Column<self, %s> $%s */', $docCommentType, $columnName));
$constructor->addBody('$? = Column::from($this, self::getDatabaseColumns()[?], $this->typeResolver);', [$columnName, $columnName]);
$columnInitializers[$columnName] = new Code\PhpLiteral('$?', [$columnName]);
$columnInitializers[$columnName] = new Code\Literal('$?', [$columnName]);
}
$columnsProperty->addComment(\sprintf('@var array{%s}', \implode(', ', $columnsShape)));
......@@ -297,7 +333,7 @@ final class TableImplementation implements Capability
private function implementConfigMethodReturningClass(Code\PhpNamespace $namespace, Code\ClassType $classType, string $name, string $class): void
{
$namespace->addUse($class);
$this->implementConfigMethod($classType, $name, new Code\PhpLiteral($namespace->simplifyName($class) . '::class'));
$this->implementConfigMethod($classType, $name, new Code\Literal($namespace->simplifyName($class) . '::class'));
}
private function implementConfigMethod(Code\ClassType $classType, string $name, mixed $value): void
......
......@@ -11,6 +11,7 @@ use Grifart\Tables\Database\Identifier;
use Grifart\Tables\Row;
use Grifart\Tables\Type;
use Grifart\Tables\TypeResolver;
use function array_keys;
use function Functional\map;
use function Grifart\ClassScaffolder\Capabilities\constructorWithPromotedProperties;
use function Grifart\ClassScaffolder\Capabilities\getters;
......@@ -98,6 +99,7 @@ final class TablesDefinitions
$modificationsClassName,
$columnMetadata,
$columnPhpTypes,
$primaryKeyFields,
));
return Definitions::from($rowClass, $modificationsClass, $primaryKeyClass, $tableClass);
......
......@@ -20,8 +20,7 @@ $table = new PackagesTable(
TestFixtures::createTypeResolver($connection),
);
$package = $table->new('grifart/tables', [0, 8, 0], [new Version(0, 7, 0)]);
$table->save($package);
$table->new('grifart/tables', [0, 8, 0], [new Version(0, 7, 0)]);
$byVersion = $table->findBy([
$table->version()->is([0, 8, 0]),
......
......@@ -12,6 +12,7 @@ use Grifart\Tables\Column;
use Grifart\Tables\ColumnMetadata;
use Grifart\Tables\ColumnNotFound;
use Grifart\Tables\Conditions\Condition;
use Grifart\Tables\DefaultValue;
use Grifart\Tables\Expression;
use Grifart\Tables\OrderBy\OrderBy;
use Grifart\Tables\RowNotFound;
......@@ -135,34 +136,50 @@ final class PackagesTable implements Table
}
public function newEmpty(): PackageModifications
{
return PackageModifications::new();
}
/**
* @param array{int, int, int} $version
* @param Version[] $previousVersions
*/
public function new(string $name, array $version, array $previousVersions): PackageModifications
public function new(string $name, array $version, array $previousVersions): PackageRow
{
$primaryKey = PackagePrimaryKey::from(name: $name);
$modifications = PackageModifications::new();
$modifications->modifyName($name);
$modifications->modifyVersion($version);
$modifications->modifyPreviousVersions($previousVersions);
return $modifications;
$this->save($modifications);
return $this->get($primaryKey);
}
public function edit(PackageRow|PackagePrimaryKey $rowOrKey): PackageModifications
/**
* @param array{int, int, int}|DefaultValue $version
* @param Version[]|DefaultValue $previousVersions
*/
public function edit(
PackageRow|PackagePrimaryKey $rowOrKey,
string|DefaultValue $name = new DefaultValue,
array|DefaultValue $version = new DefaultValue,
array|DefaultValue $previousVersions = new DefaultValue,
): PackageRow
{
$primaryKey = $rowOrKey instanceof PackagePrimaryKey ? $rowOrKey : PackagePrimaryKey::fromRow($rowOrKey);
return PackageModifications::update($primaryKey);
$modifications = PackageModifications::update($primaryKey);
if (!$name instanceof DefaultValue) {
$modifications->modifyName($name);
}
if (!$version instanceof DefaultValue) {
$modifications->modifyVersion($version);
}
if (!$previousVersions instanceof DefaultValue) {
$modifications->modifyPreviousVersions($previousVersions);
}
$this->save($modifications);
return $this->get($primaryKey);
}
public function save(PackageModifications $changes): void
private function save(PackageModifications $changes): void
{
$this->tableManager->save($this, $changes);
}
......
......@@ -12,6 +12,7 @@ use Grifart\Tables\Column;
use Grifart\Tables\ColumnMetadata;
use Grifart\Tables\ColumnNotFound;
use Grifart\Tables\Conditions\Condition;
use Grifart\Tables\DefaultValue;
use Grifart\Tables\Expression;
use Grifart\Tables\OrderBy\OrderBy;
use Grifart\Tables\RowNotFound;
......@@ -135,29 +136,44 @@ final class TestsTable implements Table
}
public function newEmpty(): TestModifications
{
return TestModifications::new();
}
public function new(Uuid $id, int $score): TestModifications
public function new(Uuid $id, int $score, string|DefaultValue|null $details = new DefaultValue): TestRow
{
$primaryKey = TestPrimaryKey::from(id: $id);
$modifications = TestModifications::new();
$modifications->modifyId($id);
$modifications->modifyScore($score);
return $modifications;
if (!$details instanceof DefaultValue) {
$modifications->modifyDetails($details);
}
$this->save($modifications);
return $this->get($primaryKey);
}
public function edit(TestRow|TestPrimaryKey $rowOrKey): TestModifications
public function edit(
TestRow|TestPrimaryKey $rowOrKey,
Uuid|DefaultValue $id = new DefaultValue,
int|DefaultValue $score = new DefaultValue,
string|DefaultValue|null $details = new DefaultValue,
): TestRow
{
$primaryKey = $rowOrKey instanceof TestPrimaryKey ? $rowOrKey : TestPrimaryKey::fromRow($rowOrKey);
return TestModifications::update($primaryKey);
$modifications = TestModifications::update($primaryKey);
if (!$id instanceof DefaultValue) {
$modifications->modifyId($id);
}
if (!$score instanceof DefaultValue) {
$modifications->modifyScore($score);
}
if (!$details instanceof DefaultValue) {
$modifications->modifyDetails($details);
}
$this->save($modifications);
return $this->get($primaryKey);
}
public function save(TestModifications $changes): void
private function save(TestModifications $changes): void
{
$this->tableManager->save($this, $changes);
}
......
......@@ -37,9 +37,7 @@ Assert::count(2, $all);
$all2 = $table->findBy([]);
Assert::equal($all, $all2);
$changeSet = $table->new(new Uuid('9493decd-4b9c-45d6-9960-0c94dc9be353'), -5);
$changeSet->modifyDetails('👎');
$table->save($changeSet);
$table->new(new Uuid('9493decd-4b9c-45d6-9960-0c94dc9be353'), -5, details: '👎');
$all = $table->getAll();
Assert::count(3, $all);
......@@ -115,12 +113,11 @@ Assert::same(0, $nullDetails[0]->getScore());
$unique = $table->getBy($table->score()->is(42));
Assert::same(42, $unique->getScore());
$zero = $table->get(TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')));
$zeroChangeSet = $table->edit($zero);
$zeroChangeSet->modifyDetails('nada');
$table->save($zeroChangeSet);
$updatedZero = $table->get(TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')));
$updatedZero = $table->edit(
TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')),
details: 'nada',
);
Assert::same(0, $updatedZero->getScore());
Assert::same('nada', $updatedZero->getDetails());
$table->delete(TestPrimaryKey::fromRow($updatedZero));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment