Skip to content

Commit

Permalink
Support for generated fields (#95)
Browse files Browse the repository at this point in the history
Co-authored-by: roxblnfk <[email protected]>
  • Loading branch information
msmakouz and roxblnfk authored Feb 8, 2024
1 parent 5565329 commit 7dad356
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 3 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
],
"require": {
"php": ">=8.1",
"cycle/orm": "^2.2.0",
"cycle/schema-builder": "^2.6",
"cycle/orm": "^2.7",
"cycle/schema-builder": "^2.8",
"spiral/attributes": "^2.8|^3.0",
"spiral/tokenizer": "^2.8|^3.0",
"doctrine/inflector": "^2.0"
Expand Down
37 changes: 37 additions & 0 deletions src/Annotation/GeneratedValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Annotation;

use Cycle\ORM\Schema\GeneratedField;
use Spiral\Attributes\NamedArgumentConstructor;

/**
* @Annotation
* @NamedArgumentConstructor
* @Target({"PROPERTY"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
#[NamedArgumentConstructor]
class GeneratedValue
{
public function __construct(
protected bool $beforeInsert = false,
protected bool $onInsert = false,
protected bool $beforeUpdate = false,
) {
}

public function getFlags(): ?int
{
if (!$this->beforeInsert && !$this->onInsert && !$this->beforeUpdate) {
return null;
}

return
($this->beforeInsert ? GeneratedField::BEFORE_INSERT : 0) |
($this->onInsert ? GeneratedField::ON_INSERT : 0) |
($this->beforeUpdate ? GeneratedField::BEFORE_UPDATE : 0);
}
}
29 changes: 28 additions & 1 deletion src/Configurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
use Cycle\Annotated\Annotation\Embeddable;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\ForeignKey;
use Cycle\Annotated\Annotation\GeneratedValue;
use Cycle\Annotated\Annotation\Relation as RelationAnnotation;
use Cycle\Annotated\Exception\AnnotationException;
use Cycle\Annotated\Exception\AnnotationRequiredArgumentsException;
use Cycle\Annotated\Exception\AnnotationWrongTypeArgumentException;
use Cycle\Annotated\Utils\EntityUtils;
use Cycle\ORM\Schema\GeneratedField;
use Cycle\Schema\Definition\Entity as EntitySchema;
use Cycle\Schema\Definition\ForeignKey as ForeignKeySchema;
use Cycle\Schema\Definition\Field;
use Cycle\Schema\Definition\ForeignKey as ForeignKeySchema;
use Cycle\Schema\Definition\Relation;
use Cycle\Schema\Generator\SyncTables;
use Cycle\Schema\SchemaModifierInterface;
Expand Down Expand Up @@ -232,6 +234,9 @@ public function initField(string $name, Column $column, \ReflectionClass $class,
$field->setColumn($columnName);

$field->setPrimary($column->isPrimary());
if ($this->isOnInsertGeneratedField($field)) {
$field->setGenerated(GeneratedField::ON_INSERT);
}

$field->setTypecast($this->resolveTypecast($column->getTypecast(), $class));

Expand Down Expand Up @@ -297,6 +302,20 @@ public function initForeignKeys(Entity $ann, EntitySchema $entity, \ReflectionCl
}
}

public function initGeneratedFields(EntitySchema $entity, \ReflectionClass $class): void
{
foreach ($class->getProperties() as $property) {
try {
$generated = $this->reader->firstPropertyMetadata($property, GeneratedValue::class);
if ($generated !== null) {
$entity->getFields()->get($property->getName())->setGenerated($generated->getFlags());
}
} catch (\Throwable $e) {
throw new AnnotationException($e->getMessage(), (int) $e->getCode(), $e);
}
}
}

/**
* Resolve class or role name relative to the current class.
*
Expand Down Expand Up @@ -386,4 +405,12 @@ private function getPropertyMetadata(\ReflectionProperty $property, string $name
throw new AnnotationException($e->getMessage(), (int) $e->getCode(), $e);
}
}

private function isOnInsertGeneratedField(Field $field): bool
{
return match ($field->getType()) {
'serial', 'bigserial', 'smallserial' => true,
default => $field->isPrimary()
};
}
}
3 changes: 3 additions & 0 deletions src/Entities.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public function run(Registry $registry): Registry
continue;
}

// generated fields
$this->generator->initGeneratedFields($e, $entity->class);

// register entity (OR find parent)
$registry->register($e);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Fixtures\Fixtures25\PostgreSQL;

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;

/**
* @Entity(role="withGeneratedSerial", table="with_generated_serial")
*/
#[Entity(role: 'withGeneratedSerial', table: 'with_generated_serial')]
class WithGeneratedSerial
{
/**
* @Column(type="primary")
*/
#[Column(type: 'primary')]
public int $id;

/**
* @Column(type="smallserial", name="small_serial")
*/
#[Column(type: 'smallserial', name: 'small_serial')]
public int $smallSerial;

/**
* @Column(type="serial")
*/
#[Column(type: 'serial')]
public int $serial;

/**
* @Column(type="bigserial", name="big_serial")
*/
#[Column(type: 'bigserial', name: 'big_serial')]
public int $bigSerial;
}
52 changes: 52 additions & 0 deletions tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Fixtures\Fixtures25;

use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\GeneratedValue;

/**
* @Entity(role="withGeneratedFields", table="with_generated_fields")
*/
#[Entity(role: 'withGeneratedFields', table: 'with_generated_fields')]
class WithGeneratedFields
{
/**
* @Column(type="primary")
*/
#[Column(type: 'primary')]
public int $id;

/**
* @Column(type="datetime", name="created_at")
* @GeneratedValue(beforeInsert=true)
*/
#[
Column(type: 'datetime', name: 'created_at'),
GeneratedValue(beforeInsert: true)
]
public \DateTimeImmutable $createdAt;

/**
* @Column(type="datetime", name="created_at_generated_by_database")
* @GeneratedValue(onInsert=true)
*/
#[
Column(type: 'datetime', name: 'created_at_generated_by_database'),
GeneratedValue(onInsert: true)
]
public \DateTimeImmutable $createdAtGeneratedByDatabase;

/**
* @Column(type="datetime", name="created_at")
* @GeneratedValue(beforeInsert=true, beforeUpdate=true)
*/
#[
Column(type: 'datetime', name: 'updated_at'),
GeneratedValue(beforeInsert: true, beforeUpdate: true)
]
public \DateTimeImmutable $updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Functional\Driver\Common;

use Cycle\Annotated\Entities;
use Cycle\Annotated\Locator\TokenizerEntityLocator;
use Cycle\ORM\Schema\GeneratedField;
use Cycle\ORM\SchemaInterface;
use Cycle\Schema\Compiler;
use Cycle\Schema\Registry;
use PHPUnit\Framework\Attributes\DataProvider;
use Spiral\Attributes\ReaderInterface;
use Spiral\Tokenizer\Config\TokenizerConfig;
use Spiral\Tokenizer\Tokenizer;

abstract class GeneratedFieldsTestCase extends BaseTestCase
{
#[DataProvider('allReadersProvider')]
public function testGeneratedFields(ReaderInterface $reader): void
{
$tokenizer = new Tokenizer(new TokenizerConfig([
'directories' => [__DIR__ . '/../../../Fixtures/Fixtures25'],
'exclude' => ['PostgreSQL'],
]));

$locator = $tokenizer->classLocator();

$r = new Registry($this->dbal);
$schema = (new Compiler())->compile($r, [
new Entities(new TokenizerEntityLocator($locator, $reader), $reader),
]);

$this->assertSame(
[
'id' => GeneratedField::ON_INSERT,
'createdAt' => GeneratedField::BEFORE_INSERT,
'createdAtGeneratedByDatabase' => GeneratedField::ON_INSERT,
'updatedAt' => GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE,
],
$schema['withGeneratedFields'][SchemaInterface::GENERATED_FIELDS]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Cycle\ORM\Relation;
use Cycle\ORM\Schema;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Schema\GeneratedField;
use Cycle\ORM\Select\Repository;
use Cycle\ORM\Select\Source;
use Cycle\ORM\EntityManager;
Expand Down Expand Up @@ -306,6 +307,9 @@ public function testSingleTableInheritanceWithDifferentColumnDeclaration(
],
SchemaInterface::SCHEMA => [],
SchemaInterface::TYPECAST_HANDLER => null,
SchemaInterface::GENERATED_FIELDS => [
'id' => GeneratedField::ON_INSERT,
],
],
$schema['comment']
);
Expand Down
16 changes: 16 additions & 0 deletions tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Functional\Driver\MySQL;

// phpcs:ignore
use Cycle\Annotated\Tests\Functional\Driver\Common\GeneratedFieldsTestCase;
use PHPUnit\Framework\Attributes\Group;

#[Group('driver')]
#[Group('driver-mysql')]
final class GeneratedFieldsTest extends GeneratedFieldsTestCase
{
public const DRIVER = 'mysql';
}
53 changes: 53 additions & 0 deletions tests/Annotated/Functional/Driver/Postgres/GeneratedFieldsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Functional\Driver\Postgres;

// phpcs:ignore
use Cycle\Annotated\Entities;
use Cycle\Annotated\Locator\TokenizerEntityLocator;
use Cycle\Annotated\Tests\Functional\Driver\Common\GeneratedFieldsTestCase;
use Cycle\ORM\Schema\GeneratedField;
use Cycle\ORM\SchemaInterface;
use Cycle\Schema\Compiler;
use Cycle\Schema\Registry;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Spiral\Attributes\ReaderInterface;
use Spiral\Tokenizer\Config\TokenizerConfig;
use Spiral\Tokenizer\Tokenizer;

#[Group('driver')]
#[Group('driver-postgres')]
final class GeneratedFieldsTest extends GeneratedFieldsTestCase
{
public const DRIVER = 'postgres';

#[DataProvider('allReadersProvider')]
public function testSerialGeneratedFields(ReaderInterface $reader): void
{
$tokenizer = new Tokenizer(new TokenizerConfig([
'directories' => [__DIR__ . '/../../../Fixtures/Fixtures25/PostgreSQL'],
'exclude' => [],
]));

$locator = $tokenizer->classLocator();

$r = new Registry($this->dbal);

$schema = (new Compiler())->compile($r, [
new Entities(new TokenizerEntityLocator($locator, $reader), $reader),
]);

$this->assertSame(
[
'id' => GeneratedField::ON_INSERT,
'smallSerial' => GeneratedField::ON_INSERT,
'serial' => GeneratedField::ON_INSERT,
'bigSerial' => GeneratedField::ON_INSERT,
],
$schema['withGeneratedSerial'][SchemaInterface::GENERATED_FIELDS]
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Functional\Driver\SQLServer;

// phpcs:ignore
use Cycle\Annotated\Tests\Functional\Driver\Common\GeneratedFieldsTestCase;
use PHPUnit\Framework\Attributes\Group;

#[Group('driver')]
#[Group('driver-sqlserver')]
final class GeneratedFieldsTest extends GeneratedFieldsTestCase
{
public const DRIVER = 'sqlserver';
}
16 changes: 16 additions & 0 deletions tests/Annotated/Functional/Driver/SQLite/GeneratedFieldsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Cycle\Annotated\Tests\Functional\Driver\SQLite;

// phpcs:ignore
use Cycle\Annotated\Tests\Functional\Driver\Common\GeneratedFieldsTestCase;
use PHPUnit\Framework\Attributes\Group;

#[Group('driver')]
#[Group('driver-sqlite')]
final class GeneratedFieldsTest extends GeneratedFieldsTestCase
{
public const DRIVER = 'sqlite';
}

0 comments on commit 7dad356

Please sign in to comment.