Skip to content

Commit

Permalink
Merge pull request #789 from cakephp/foreign-key-builder
Browse files Browse the repository at this point in the history
Port changes from cakephp/phinx#2322
  • Loading branch information
dereuromark authored Dec 21, 2024
2 parents 2faa558 + cce96af commit 744b970
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/en/writing-migrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,7 @@ Option Description
update set an action to be triggered when the row is updated
delete set an action to be triggered when the row is deleted
constraint set a name to be used by foreign key constraint
deferrable define deferred constraint application (postgres only)
========== ===========

You can pass one or more of these options to any column with the optional
Expand Down
3 changes: 3 additions & 0 deletions src/Db/Adapter/PostgresAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,9 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta
if ($foreignKey->getOnUpdate()) {
$def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
}
if ($foreignKey->getDeferrableMode()) {
$def .= " {$foreignKey->getDeferrableMode()}";
}

return $def;
}
Expand Down
58 changes: 57 additions & 1 deletion src/Db/Table/ForeignKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ class ForeignKey
public const RESTRICT = 'RESTRICT';
public const SET_NULL = 'SET NULL';
public const NO_ACTION = 'NO ACTION';
public const DEFERRED = 'DEFERRABLE INITIALLY DEFERRED';
public const IMMEDIATE = 'DEFERRABLE INITIALLY IMMEDIATE';
public const NOT_DEFERRED = 'NOT DEFERRABLE';

/**
* @var array<string>
*/
protected static array $validOptions = ['delete', 'update', 'constraint'];
protected static array $validOptions = ['delete', 'update', 'constraint', 'deferrable'];

/**
* @var string[]
Expand Down Expand Up @@ -53,6 +56,11 @@ class ForeignKey
*/
protected ?string $constraint = null;

/**
* @var string|null
*/
protected ?string $deferrableMode = null;

/**
* Sets the foreign key columns.
*
Expand Down Expand Up @@ -191,6 +199,27 @@ public function getConstraint(): ?string
return $this->constraint;
}

/**
* Sets deferrable mode for the foreign key.
*
* @param string $deferrableMode Constraint
* @return $this
*/
public function setDeferrableMode(string $deferrableMode)
{
$this->deferrableMode = $this->normalizeDeferrable($deferrableMode);

return $this;
}

/**
* Gets deferrable mode for the foreign key.
*/
public function getDeferrableMode(): ?string
{
return $this->deferrableMode;
}

/**
* Utility method that maps an array of index options to this objects methods.
*
Expand All @@ -210,6 +239,8 @@ public function setOptions(array $options)
$this->setOnDelete($value);
} elseif ($option === 'update') {
$this->setOnUpdate($value);
} elseif ($option === 'deferrable') {
$this->setDeferrableMode($value);
} else {
$method = 'set' . ucfirst($option);
$this->$method($value);
Expand All @@ -235,4 +266,29 @@ protected function normalizeAction(string $action): string

return constant($constantName);
}

/**
* From passed value checks if it's correct and fixes if needed
*
* @param string $deferrable Deferrable
* @throws \InvalidArgumentException
* @return string
*/
protected function normalizeDeferrable(string $deferrable): string
{
$mapping = [
'DEFERRED' => ForeignKey::DEFERRED,
'IMMEDIATE' => ForeignKey::IMMEDIATE,
'NOT DEFERRED' => ForeignKey::NOT_DEFERRED,
ForeignKey::DEFERRED => ForeignKey::DEFERRED,
ForeignKey::IMMEDIATE => ForeignKey::IMMEDIATE,
ForeignKey::NOT_DEFERRED => ForeignKey::NOT_DEFERRED,
];
$normalized = strtoupper(str_replace('_', ' ', $deferrable));
if (array_key_exists($normalized, $mapping)) {
return $mapping[$normalized];
}

throw new InvalidArgumentException('Unknown deferrable passed: ' . $deferrable);
}
}
37 changes: 37 additions & 0 deletions tests/TestCase/Db/Table/ForeignKeyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,41 @@ public function testSetOptionThrowsExceptionIfOptionIsNotString()

$this->fk->setOptions(['update']);
}

#[DataProvider('deferrableProvider')]
public function testDeferrableCanBeSetThroughSetters(string $dirtyValue, string $valueOfConstant): void
{
$this->fk->setDeferrableMode($dirtyValue);
$this->assertEquals($valueOfConstant, $this->fk->getDeferrableMode());
}

#[DataProvider('deferrableProvider')]
public function testDeferrableCanBeSetThroughOptions(string $dirtyValue, string $valueOfConstant): void
{
$this->fk->setOptions([
'deferrable' => $dirtyValue,
]);
$this->assertEquals($valueOfConstant, $this->fk->getDeferrableMode());
}

public static function deferrableProvider(): array
{
return [
['DEFERRED', ForeignKey::DEFERRED],
['IMMEDIATE', ForeignKey::IMMEDIATE],
['NOT_DEFERRED', ForeignKey::NOT_DEFERRED],
['Deferred', ForeignKey::DEFERRED],
['Immediate', ForeignKey::IMMEDIATE],
['Not_deferred', ForeignKey::NOT_DEFERRED],
[ForeignKey::DEFERRED, ForeignKey::DEFERRED],
[ForeignKey::IMMEDIATE, ForeignKey::IMMEDIATE],
[ForeignKey::NOT_DEFERRED, ForeignKey::NOT_DEFERRED],
];
}

public function testThrowsErrorForInvalidDeferrableValue(): void
{
$this->expectException(InvalidArgumentException::class);
$this->fk->setDeferrableMode('invalid_value');
}
}

0 comments on commit 744b970

Please sign in to comment.