Skip to content

Commit

Permalink
Fix typecasts list merging; add Defaults registry (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
msmakouz authored May 19, 2023
1 parent b60830d commit 92ccd4c
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 36 deletions.
42 changes: 24 additions & 18 deletions src/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

namespace Cycle\Schema;

use Cycle\ORM\Mapper\Mapper;
use Cycle\ORM\SchemaInterface as Schema;
use Cycle\ORM\Select\Repository;
use Cycle\ORM\Select\Source;
use Cycle\Schema\Definition\Comparator\FieldComparator;
use Cycle\Schema\Definition\Entity;
use Cycle\Schema\Definition\Field;
Expand All @@ -25,23 +22,14 @@ final class Compiler
/** @var array<non-empty-string, array<int, mixed>> */
private array $result = [];

/** @var array<int, mixed> */
private array $defaults = [
Schema::MAPPER => Mapper::class,
Schema::REPOSITORY => Repository::class,
Schema::SOURCE => Source::class,
Schema::SCOPE => null,
Schema::TYPECAST_HANDLER => null,
];

/**
* Compile the registry schema.
*
* @param GeneratorInterface[] $generators
*/
public function compile(Registry $registry, array $generators = [], array $defaults = []): array
{
$this->defaults = $defaults + $this->defaults;
$registry->getDefaults()->merge($defaults);

foreach ($generators as $generator) {
if (!$generator instanceof GeneratorInterface) {
Expand Down Expand Up @@ -79,14 +67,16 @@ public function getSchema(): array
*/
private function compute(Registry $registry, Entity $entity): void
{
$defaults = $registry->getDefaults();

$schema = [
Schema::ENTITY => $entity->getClass(),
Schema::SOURCE => $entity->getSource() ?? $this->defaults[Schema::SOURCE],
Schema::MAPPER => $entity->getMapper() ?? $this->defaults[Schema::MAPPER],
Schema::REPOSITORY => $entity->getRepository() ?? $this->defaults[Schema::REPOSITORY],
Schema::SCOPE => $entity->getScope() ?? $this->defaults[Schema::SCOPE],
Schema::SOURCE => $entity->getSource() ?? $defaults[Schema::SOURCE],
Schema::MAPPER => $entity->getMapper() ?? $defaults[Schema::MAPPER],
Schema::REPOSITORY => $entity->getRepository() ?? $defaults[Schema::REPOSITORY],
Schema::SCOPE => $entity->getScope() ?? $defaults[Schema::SCOPE],
Schema::SCHEMA => $entity->getSchema(),
Schema::TYPECAST_HANDLER => $entity->getTypecast() ?? $this->defaults[Schema::TYPECAST_HANDLER],
Schema::TYPECAST_HANDLER => $this->renderTypecastHandler($registry->getDefaults(), $entity),
Schema::PRIMARY_KEY => $entity->getPrimaryFields()->getNames(),
Schema::COLUMNS => $this->renderColumns($entity),
Schema::FIND_BY_KEYS => $this->renderReferences($entity),
Expand Down Expand Up @@ -226,4 +216,20 @@ private function renderRelations(Registry $registry, Entity $entity, array &$sch
$relation->modifySchema($schema);
}
}

private function renderTypecastHandler(Defaults $defaults, Entity $entity): array|null|string
{
$defaults = $defaults[Schema::TYPECAST_HANDLER] ?? [];
if (!\is_array($defaults)) {
$defaults = [$defaults];
}

if ($defaults === []) {
return $entity->getTypecast();
}

$typecast = $entity->getTypecast() ?? [];

return \array_values(\array_unique(\array_merge(\is_array($typecast) ? $typecast : [$typecast], $defaults)));
}
}
57 changes: 57 additions & 0 deletions src/Defaults.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema;

use Cycle\ORM\Mapper\Mapper;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Select\Repository;
use Cycle\ORM\Select\Source;

final class Defaults implements \ArrayAccess
{
/**
* @param array<int, mixed> $defaults
*/
public function __construct(
private array $defaults = [
SchemaInterface::MAPPER => Mapper::class,
SchemaInterface::REPOSITORY => Repository::class,
SchemaInterface::SOURCE => Source::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => null,
]
) {
}

/**
* @param array<int, mixed> $defaults
*/
public function merge(array $defaults): self
{
$this->defaults = $defaults + $this->defaults;

return $this;
}

public function offsetExists(mixed $offset): bool
{
return isset($this->defaults[$offset]);
}

public function offsetGet(mixed $offset): mixed
{
return $this->defaults[$offset];
}

public function offsetSet(mixed $offset, mixed $value): void
{
$this->defaults[$offset] = $value;
}

public function offsetUnset(mixed $offset): void
{
unset($this->defaults[$offset]);
}
}
9 changes: 8 additions & 1 deletion src/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ final class Registry implements \IteratorAggregate
private \SplObjectStorage $tables;
private \SplObjectStorage $children;
private \SplObjectStorage $relations;
private Defaults $defaults;

/**
* @param DatabaseProviderInterface $dbal
*/
public function __construct(DatabaseProviderInterface $dbal)
public function __construct(DatabaseProviderInterface $dbal, ?Defaults $defaults = null)
{
$this->dbal = $dbal;
$this->tables = new \SplObjectStorage();
$this->children = new \SplObjectStorage();
$this->relations = new \SplObjectStorage();
$this->defaults = $defaults ?? new Defaults();
}

public function register(Entity $entity): self
Expand Down Expand Up @@ -270,6 +272,11 @@ public function getRelations(Entity $entity): array
return $this->relations[$entity];
}

public function getDefaults(): Defaults
{
return $this->defaults;
}

protected function hasInstance(Entity $entity): bool
{
return array_search($entity, $this->entities, true) !== false;
Expand Down
48 changes: 44 additions & 4 deletions tests/Schema/CompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Cycle\Schema\Tests;

use Cycle\Database\DatabaseProviderInterface;
use Cycle\ORM\Parser\Typecast;
use Cycle\ORM\SchemaInterface;
use Cycle\Schema\Compiler;
use Cycle\Schema\Definition\Entity;
use Cycle\Schema\Definition\Field;
Expand All @@ -13,14 +15,15 @@
use Cycle\Schema\Registry;
use Cycle\Schema\Tests\Fixtures\Author;
use Cycle\Schema\Tests\Fixtures\BrokenSchemaModifier;
use Cycle\Schema\Tests\Fixtures\Typecaster;
use PHPUnit\Framework\TestCase;

class CompilerTest extends TestCase
{
public function testWrongGeneratorShouldThrowAnException()
public function testWrongGeneratorShouldThrowAnException(): void
{
$this->expectException(CompilerException::class);
$this->expectErrorMessage(
$this->expectExceptionMessage(
'Invalid generator `\'Cycle\\\\Schema\\\\Tests\\\\Fixtures\\\\Author\'`. '
. 'It should implement `Cycle\Schema\GeneratorInterface` interface.'
);
Expand All @@ -40,10 +43,10 @@ public function testWrongGeneratorShouldThrowAnException()
]);
}

public function testWrongEntitySchemaModifierShouldThrowAnException()
public function testWrongEntitySchemaModifierShouldThrowAnException(): void
{
$this->expectException(SchemaModifierException::class);
$this->expectErrorMessage(
$this->expectExceptionMessage(
'Unable to apply schema modifier `Cycle\Schema\Tests\Fixtures\BrokenSchemaModifier` '
. 'for the `author` role. Something went wrong'
);
Expand All @@ -62,4 +65,41 @@ public function testWrongEntitySchemaModifierShouldThrowAnException()

(new Compiler())->compile($r);
}

/**
* @dataProvider renderTypecastDataProvider
*/
public function testRenderTypecast(mixed $expected, array $defaults, mixed $entityTypecast = null): void
{
$entity = new Entity();
$entity->setRole('author')->setClass(Author::class);
$entity->getFields()->set('id', (new Field())->setType('primary')->setColumn('id'));
if ($entityTypecast) {
$entity->setTypecast($entityTypecast);
}

$r = new Registry($this->createMock(DatabaseProviderInterface::class));
$r->register($entity);

$schema = (new Compiler())->compile($r, [], $defaults);

$this->assertSame($expected, $schema['author'][SchemaInterface::TYPECAST_HANDLER]);
}

public static function renderTypecastDataProvider(): \Traversable
{
yield [null, []];
yield [Typecaster::class, [], Typecaster::class];
yield [[Typecaster::class], [SchemaInterface::TYPECAST_HANDLER => Typecaster::class]];
yield [
[Typecaster::class, Typecast::class],
[SchemaInterface::TYPECAST_HANDLER => Typecast::class],
Typecaster::class,
];
yield [
[Typecaster::class, Typecast::class],
[SchemaInterface::TYPECAST_HANDLER => [Typecaster::class, Typecast::class]],
Typecaster::class,
];
}
}
123 changes: 123 additions & 0 deletions tests/Schema/DefaultsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema\Tests;

use Cycle\ORM\Mapper\Mapper;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Select\Repository;
use Cycle\ORM\Select\Source;
use Cycle\Schema\Defaults;
use PHPUnit\Framework\TestCase;

final class DefaultsTest extends TestCase
{
public function testDefaultValues(): void
{
$defaults = new Defaults();

$this->assertSame(Mapper::class, $defaults[SchemaInterface::MAPPER]);
$this->assertSame(Repository::class, $defaults[SchemaInterface::REPOSITORY]);
$this->assertSame(Source::class, $defaults[SchemaInterface::SOURCE]);
$this->assertNull($defaults[SchemaInterface::SCOPE]);
$this->assertNull($defaults[SchemaInterface::TYPECAST_HANDLER]);
}

/**
* @dataProvider mergeDataProvider
*/
public function testMerge(array $expected, array $values): void
{
$defaults = new Defaults();
$defaults->merge($values);

$ref = new \ReflectionProperty($defaults, 'defaults');
$ref->setAccessible(true);

$this->assertEquals($expected, $ref->getValue($defaults));
}

public function testOffsetExists(): void
{
$defaults = new Defaults();

$this->assertTrue($defaults->offsetExists(SchemaInterface::MAPPER));
$this->assertFalse($defaults->offsetExists('foo'));
}

public function testOffsetGet(): void
{
$defaults = new Defaults();

$this->assertSame(Mapper::class, $defaults->offsetGet(SchemaInterface::MAPPER));
}

public function testOffsetSet(): void
{
$defaults = new Defaults();
$defaults->offsetSet('foo', 'bar');

$this->assertSame('bar', $defaults->offsetGet('foo'));
}

public function testOffsetUnset(): void
{
$defaults = new Defaults();

$this->assertTrue($defaults->offsetExists(SchemaInterface::MAPPER));
$defaults->offsetUnset(SchemaInterface::MAPPER);
$this->assertFalse($defaults->offsetExists(SchemaInterface::MAPPER));
}

public static function mergeDataProvider(): \Traversable
{
yield [
[
SchemaInterface::MAPPER => Mapper::class,
SchemaInterface::REPOSITORY => Repository::class,
SchemaInterface::SOURCE => Source::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => null,
],
[],
];
yield [
[
SchemaInterface::MAPPER => Mapper::class,
SchemaInterface::REPOSITORY => Repository::class,
SchemaInterface::SOURCE => Source::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => null,
'foo' => 'bar',
],
[
'foo' => 'bar',
],
];
yield [
[
SchemaInterface::MAPPER => Mapper::class,
SchemaInterface::REPOSITORY => Repository::class,
SchemaInterface::SOURCE => Source::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => 'foo',
],
[
SchemaInterface::TYPECAST_HANDLER => 'foo',
],
];
yield [
[
SchemaInterface::MAPPER => null,
SchemaInterface::REPOSITORY => Repository::class,
SchemaInterface::SOURCE => Source::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => null,
],
[
SchemaInterface::MAPPER => null,
],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function testJoinedTableWithoutOuterKeyShouldBeAddedToSchema()
public function testJoinedTableWithNonExistsOuterKeyShouldThrowAnException()
{
$this->expectException(WrongParentKeyColumnException::class);
$this->expectErrorMessage('Outer key column `foo_bar` is not found among fields of the `user` role.');
$this->expectExceptionMessage('Outer key column `foo_bar` is not found among fields of the `user` role.');

$r = new Registry(
$this->createMock(DatabaseProviderInterface::class)
Expand Down
Loading

0 comments on commit 92ccd4c

Please sign in to comment.