Skip to content

Commit

Permalink
Add basic foreign key storage/validation to Table.
Browse files Browse the repository at this point in the history
  • Loading branch information
markstory committed Jun 6, 2013
1 parent b28143f commit 5fa500c
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 2 deletions.
41 changes: 39 additions & 2 deletions lib/Cake/Database/Schema/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ class Table {
'type' => null,
'columns' => [],
'length' => [],
'references' => null,
'update' => null,
'delete' => null,
];

/**
Expand Down Expand Up @@ -223,6 +226,7 @@ public function addIndex($name, $attrs) {
}
$attrs = array_intersect_key($attrs, $this->_indexKeys);
$attrs = $attrs + $this->_indexKeys;
unset($attrs['references'], $attrs['update'], $attrs['delete']);

if (!in_array($attrs['type'], $this->_validIndexTypes, true)) {
throw new Exception(__d('cake_dev', 'Invalid index type "%s"', $attrs['type']));
Expand Down Expand Up @@ -283,8 +287,12 @@ public function primaryKey() {
*
* - `type` The type of constraint being added.
* - `columns` The columns in the index.
* - `references` The table, column a foreign key references.
* - `update` The behavior on update. Options are 'restrict', `null`, 'cascade', 'none'.
* - `delete` The behavior on delete. Options are 'restrict', `null`, 'cascade', 'none'.
*
* The default for 'update' & 'delete' is 'cascade'.
*
* @TODO implement foreign keys.
* @param string $name The name of the constraint.
* @param array $attrs The attributes for the constraint.
* @return Table $this
Expand All @@ -296,7 +304,6 @@ public function addConstraint($name, $attrs) {
}
$attrs = array_intersect_key($attrs, $this->_indexKeys);
$attrs = $attrs + $this->_indexKeys;

if (!in_array($attrs['type'], $this->_validConstraintTypes, true)) {
throw new Exception(__d('cake_dev', 'Invalid constraint type "%s"', $attrs['type']));
}
Expand All @@ -305,10 +312,40 @@ public function addConstraint($name, $attrs) {
throw new Exception(__d('cake_dev', 'Columns used in constraints must already exist.'));
}
}
if ($attrs['type'] === static::CONSTRAINT_FOREIGN) {
$attrs = $this->_checkForeignKey($attrs);
} else {
unset($attrs['references'], $attrs['update'], $attrs['delete']);
}
$this->_constraints[$name] = $attrs;
return $this;
}

/**
* Helper method to check/validate foreign keys.
*
* @param array $attrs Attributes to set.
* @return array
*/
protected function _checkForeignKey($attrs) {
$attrs += [
'references' => [],
'update' => 'cascade',
'delete' => 'cascade',
];
if (count($attrs['references']) < 2) {
throw new Exception(__d('cake_dev', 'References must contain a table and column.'));
}
$validActions = ['cascade', 'restrict', null, 'none'];
if (!in_array($attrs['update'], $validActions)) {
throw new Exception(__d('cake_dev', 'Update action is invalid.'));
}
if (!in_array($attrs['delete'], $validActions)) {
throw new Exception(__d('cake_dev', 'Delete action is invalid.'));
}
return $attrs;
}

/**
* Get the names of all the constraints in the table.
*
Expand Down
60 changes: 60 additions & 0 deletions lib/Cake/Test/TestCase/Database/Schema/TableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,64 @@ public function testOptions() {
$this->assertEquals($options, $table->options());
}

/**
* Add a basic foreign key constraint.
*
* @return void
*/
public function testAddConstraintForeignKey() {
$table = new Table('articles');
$table->addColumn('author_id', 'integer')
->addConstraint('author_id_idx', [
'type' => Table::CONSTRAINT_FOREIGN,
'columns' => ['author_id'],
'references' => ['authors', 'id'],
'update' => 'cascade',
'delete' => 'cascade',
]);
$this->assertEquals(['author_id_idx'], $table->constraints());
}

/**
* Provider for exceptionally bad foreign key data.
*
* @return array
*/
public static function badForeignKeyProvider()
{
return [
'references is bad' => [[
'type' => Table::CONSTRAINT_FOREIGN,
'columns' => ['author_id'],
'references' => ['authors'],
'delete' => 'derp',
]],
'bad update value' => [[
'type' => Table::CONSTRAINT_FOREIGN,
'columns' => ['author_id'],
'references' => ['authors', 'id'],
'update' => 'derp',
]],
'bad delete value' => [[
'type' => Table::CONSTRAINT_FOREIGN,
'columns' => ['author_id'],
'references' => ['authors', 'id'],
'delete' => 'derp',
]],
];
}

/**
* Add a foreign key constraint with bad data
*
* @dataProvider badForeignKeyProvider
* @expectedException Cake\Database\Exception
* @return void
*/
public function testAddConstraintForeignKeyBadData($data) {
$table = new Table('articles');
$table->addColumn('author_id', 'integer')
->addConstraint('author_id_idx', $data);
}

}

0 comments on commit 5fa500c

Please sign in to comment.