Skip to content

Commit

Permalink
Merge pull request #67: Add the ability to configure foreign keys
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk authored Nov 23, 2023
2 parents db763c3 + f2bb135 commit 24284c5
Show file tree
Hide file tree
Showing 14 changed files with 493 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-mssql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
extensions: pdo, pdo_sqlsrv
mssql: 'server:2019-latest'
- php: '8.1'
extensions: pdo, pdo_sqlsrv-5.10.0beta2
extensions: pdo, pdo_sqlsrv
mssql: 'server:2019-latest'

services:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
php-version: ${{ matrix.php-versions }}
coverage: pcov
tools: pecl
extensions: mbstring, pdo, pdo_sqlite, pdo_pgsql, pdo_sqlsrv-5.10.0beta2, pdo_mysql
extensions: mbstring, pdo, pdo_sqlite, pdo_pgsql, pdo_sqlsrv, pdo_mysql
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
Expand Down
15 changes: 15 additions & 0 deletions src/Definition/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Cycle\ORM\Select\ScopeInterface;
use Cycle\ORM\Select\SourceInterface;
use Cycle\Schema\Definition\Map\FieldMap;
use Cycle\Schema\Definition\Map\ForeignKeyMap;
use Cycle\Schema\Definition\Map\OptionMap;
use Cycle\Schema\Definition\Map\RelationMap;
use Cycle\Schema\Exception\EntityException;
Expand Down Expand Up @@ -78,13 +79,15 @@ final class Entity
private ?Inheritance $inheritance = null;
/** @var class-string|null */
private ?string $stiParent = null;
private ForeignKeyMap $foreignKeys;

public function __construct()
{
$this->options = new OptionMap();
$this->fields = new FieldMap();
$this->primaryFields = new FieldMap();
$this->relations = new RelationMap();
$this->foreignKeys = new ForeignKeyMap();
}

/**
Expand All @@ -96,6 +99,7 @@ public function __clone()
$this->fields = clone $this->fields;
$this->primaryFields = clone $this->primaryFields;
$this->relations = clone $this->relations;
$this->foreignKeys = clone $this->foreignKeys;
}

public function getOptions(): OptionMap
Expand Down Expand Up @@ -241,6 +245,11 @@ public function getRelations(): RelationMap
return $this->relations;
}

public function getForeignKeys(): ForeignKeyMap
{
return $this->foreignKeys;
}

public function addSchemaModifier(SchemaModifierInterface $modifier): self
{
$this->schemaModifiers[] = $modifier;
Expand Down Expand Up @@ -284,6 +293,12 @@ public function merge(self $entity): void
$this->fields->set($name, $field);
}
}

foreach ($entity->getForeignKeys() as $foreignKey) {
if (!$this->foreignKeys->has($foreignKey)) {
$this->foreignKeys->set($foreignKey);
}
}
}

/**
Expand Down
117 changes: 117 additions & 0 deletions src/Definition/ForeignKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema\Definition;

final class ForeignKey
{
/**
* @var non-empty-string
*/
private string $target;

/**
* @var array<non-empty-string>
*/
private array $innerColumns;

/**
* @var array<non-empty-string>
*/
private array $outerColumns;

private bool $createIndex;

/**
* @var non-empty-string
*/
private string $action;

/**
* @param non-empty-string $target
*/
public function setTarget(string $target): self
{
$this->target = $target;

return $this;
}

/**
* @return non-empty-string
*/
public function getTarget(): string
{
return $this->target;
}

/**
* @param array<non-empty-string> $columns
*/
public function setInnerColumns(array $columns): self
{
$this->innerColumns = $columns;

return $this;
}

/**
* @return array<non-empty-string>
*/
public function getInnerColumns(): array
{
return $this->innerColumns;
}

/**
* @param array<non-empty-string> $columns
*/
public function setOuterColumns(array $columns): self
{
$this->outerColumns = $columns;

return $this;
}

/**
* @return array<non-empty-string>
*/
public function getOuterColumns(): array
{
return $this->outerColumns;
}

/**
* Create an index on innerKey.
*/
public function createIndex(bool $createIndex = true): self
{
$this->createIndex = $createIndex;

return $this;
}

public function isCreateIndex(): bool
{
return $this->createIndex;
}

/**
* @param non-empty-string $action
*/
public function setAction(string $action): self
{
$this->action = $action;

return $this;
}

/**
* @return non-empty-string
*/
public function getAction(): string
{
return $this->action;
}
}
72 changes: 72 additions & 0 deletions src/Definition/Map/ForeignKeyMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema\Definition\Map;

use Cycle\Schema\Definition\ForeignKey;

/**
* Manage the set of foreign keys associated with the entity.
*
* @implements \IteratorAggregate<non-empty-string, ForeignKey>
*/
final class ForeignKeyMap implements \IteratorAggregate, \Countable
{
/**
* @var array<non-empty-string, ForeignKey>
*/
private array $foreignKeys = [];

/**
* Cloning.
*/
public function __clone()
{
foreach ($this->foreignKeys as $index => $foreignKey) {
$this->foreignKeys[$index] = clone $foreignKey;
}
}

public function has(ForeignKey $foreignKey): bool
{
return isset($this->foreignKeys[$this->generateIdentifier($foreignKey)]);
}

public function set(ForeignKey $foreignKey): self
{
$this->foreignKeys[$this->generateIdentifier($foreignKey)] = $foreignKey;

return $this;
}

public function remove(ForeignKey $foreignKey): self
{
unset($this->foreignKeys[$this->generateIdentifier($foreignKey)]);

return $this;
}

public function count(): int
{
return \count($this->foreignKeys);
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->foreignKeys);
}

/**
* @return non-empty-string
*/
private function generateIdentifier(ForeignKey $foreignKey): string
{
return \sprintf(
'%s:%s:%s',
$foreignKey->getTarget(),
\implode(',', $foreignKey->getInnerColumns()),
\implode(',', $foreignKey->getOuterColumns())
);
}
}
32 changes: 32 additions & 0 deletions src/Generator/ForeignKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema\Generator;

use Cycle\Schema\GeneratorInterface;
use Cycle\Schema\Registry;

final class ForeignKeys implements GeneratorInterface
{
public function run(Registry $registry): Registry
{
foreach ($registry as $entity) {
foreach ($entity->getForeignKeys() as $fk) {
$target = $registry->getEntity($fk->getTarget());

if (!$registry->getTableSchema($target)->hasIndex($fk->getOuterColumns())) {
$registry->getTableSchema($target)->index($fk->getOuterColumns())->unique();
}

$registry->getTableSchema($entity)
->foreignKey($fk->getInnerColumns(), $fk->isCreateIndex())
->references($registry->getTable($target), $fk->getOuterColumns())
->onUpdate($fk->getAction())
->onDelete($fk->getAction());
}
}

return $registry;
}
}
53 changes: 53 additions & 0 deletions tests/Schema/Definition/ForeignKeyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Cycle\Schema\Tests\Definition;

use Cycle\Schema\Definition\ForeignKey;
use PHPUnit\Framework\TestCase;

final class ForeignKeyTest extends TestCase
{
public function testTarget(): void
{
$key = new ForeignKey();

$key->setTarget('foo');
$this->assertSame('foo', $key->getTarget());
}

public function testInnerColumns(): void
{
$key = new ForeignKey();

$key->setInnerColumns(['field']);
$this->assertSame(['field'], $key->getInnerColumns());
}

public function testOuterColumns(): void
{
$key = new ForeignKey();

$key->setOuterColumns(['field']);
$this->assertSame(['field'], $key->getOuterColumns());
}

public function testIndex(): void
{
$key = new ForeignKey();

$key->createIndex(true);
$this->assertTrue($key->isCreateIndex());
$key->createIndex(false);
$this->assertFalse($key->isCreateIndex());
}

public function testAction(): void
{
$key = new ForeignKey();

$key->setAction('CASCADE');
$this->assertSame('CASCADE', $key->getAction());
}
}
Loading

0 comments on commit 24284c5

Please sign in to comment.