diff --git a/src/Database/Schema/MysqlSchema.php b/src/Database/Schema/MysqlSchema.php index 53225f246b5..d32a1054823 100644 --- a/src/Database/Schema/MysqlSchema.php +++ b/src/Database/Schema/MysqlSchema.php @@ -389,6 +389,36 @@ public function constraintSql(Table $table, $name) return $this->_keySql($out, $data); } + public function addConstraintSql(Table $table) + { + $sqlPattern = 'ALTER TABLE %s ADD %s'; + $sql = []; + + foreach ($table->constraints() as $name) { + $constraint = $table->constraint($name); + if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) { + $sql[] = sprintf($sqlPattern, $table->name(), $this->constraintSql($table, $name)); + } + } + + return $sql; + } + + public function dropConstraintSql(Table $table) + { + $sqlPattern = 'ALTER TABLE %s DROP FOREIGN KEY %s'; + $sql = []; + + foreach ($table->constraints() as $name) { + $constraint = $table->constraint($name); + if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) { + $sql[] = sprintf($sqlPattern, $table->name(), $name); + } + } + + return $sql; + } + /** * {@inheritDoc} */ diff --git a/src/Database/Schema/Table.php b/src/Database/Schema/Table.php index 3f44d6e83fd..d6443164e51 100644 --- a/src/Database/Schema/Table.php +++ b/src/Database/Schema/Table.php @@ -565,6 +565,13 @@ public function addConstraint($name, $attrs) return $this; } + public function dropConstraint($name) + { + if (isset($this->_constraints[$name])) { + unset($this->_constraints[$name]); + } + } + /** * Check whether or not a table has an autoIncrement column defined. * @@ -710,4 +717,16 @@ public function truncateSql(ConnectionInterface $connection) $dialect = $connection->driver()->schemaDialect(); return $dialect->truncateTableSql($this); } + + public function addConstraintSql(Connection $connection) + { + $dialect = $connection->driver()->schemaDialect(); + return $dialect->addConstraintSql($this); + } + + public function dropConstraintSql(Connection $connection) + { + $dialect = $connection->driver()->schemaDialect(); + return $dialect->dropConstraintSql($this); + } } diff --git a/src/TestSuite/Fixture/FixtureManager.php b/src/TestSuite/Fixture/FixtureManager.php index 531e7f8cc56..768a1f4d83e 100644 --- a/src/TestSuite/Fixture/FixtureManager.php +++ b/src/TestSuite/Fixture/FixtureManager.php @@ -16,7 +16,6 @@ use Cake\Core\Configure; use Cake\Core\Exception\Exception; -use Cake\Database\Connection; use Cake\Datasource\ConnectionManager; use Cake\Utility\Inflector; use PDOException; @@ -57,6 +56,13 @@ class FixtureManager */ protected $_insertionMap = []; + /** + * List of TestCase class name that have been processed + * + * @var array + */ + protected $_processed = []; + /** * Inspects the test to look for unloaded fixtures and loads them * @@ -194,7 +200,7 @@ protected function _loadFixtures($test) * Runs the drop and create commands on the fixtures if necessary. * * @param \Cake\TestSuite\Fixture\TestFixture $fixture the fixture object to create - * @param Connection $db the datasource instance to use + * @param \Cake\Database\Connection $db The Connection object instance to use * @param array $sources The existing tables in the datasource. * @param bool $drop whether drop the fixture if it is already created or not * @return void @@ -240,11 +246,19 @@ public function load($test) try { $createTables = function ($db, $fixtures) use ($test) { + $db->enableForeignKeys(); $tables = $db->schemaCollection()->listTables(); $configName = $db->configName(); if (!isset($this->_insertionMap[$configName])) { $this->_insertionMap[$configName] = []; } + + foreach ($fixtures as $name => $fixture) { + if (in_array($fixture->table, $tables)) { + $fixture->dropConstraints($db); + } + } + foreach ($fixtures as $fixture) { if (!in_array($fixture, $this->_insertionMap[$configName])) { $this->_setupTable($fixture, $db, $tables, $test->dropTables); @@ -252,6 +266,10 @@ public function load($test) $fixture->truncate($db); } } + + foreach ($fixtures as $name => $fixture) { + $fixture->createConstraints($db); + } }; $this->_runOperation($fixtures, $createTables); diff --git a/src/TestSuite/Fixture/TestFixture.php b/src/TestSuite/Fixture/TestFixture.php index 4d127f3e1bc..48eed3cdfba 100644 --- a/src/TestSuite/Fixture/TestFixture.php +++ b/src/TestSuite/Fixture/TestFixture.php @@ -14,6 +14,7 @@ namespace Cake\TestSuite\Fixture; use Cake\Core\Exception\Exception as CakeException; +use Cake\Database\Connection; use Cake\Database\Schema\Table; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; @@ -79,6 +80,13 @@ class TestFixture implements FixtureInterface */ protected $_schema; + /** + * Fixture constraints to be created. + * + * @var array + */ + public $constraints = []; + /** * Instantiate the fixture. * @@ -159,7 +167,11 @@ protected function _schemaFromFields() } if (!empty($this->fields['_constraints'])) { foreach ($this->fields['_constraints'] as $name => $data) { - $this->_schema->addConstraint($name, $data); + if ($data['type'] !== 'foreign') { + $this->_schema->addConstraint($name, $data); + } else { + $this->constraints[$name] = $data; + } } } if (!empty($this->fields['_indexes'])) { @@ -284,6 +296,72 @@ public function insert(ConnectionInterface $db) return true; } + /** + * Build and execute SQL queries necessary to create the constraints for the + * fixture + * + * @param \Cake\Database\Connection $db An instance of the database into which the constraints will be created + * @return bool on success or if there are no constraints to create, or false on failure + */ + public function createConstraints(Connection $db) + { + if (empty($this->constraints)) { + return true; + } + + foreach ($this->constraints as $name => $data) { + $this->_schema->addConstraint($name, $data); + } + + $sql = $this->_schema->addConstraintSql($db); + + if (empty($sql)) { + return true; + } + + try { + foreach ($sql as $stmt) { + $db->execute($stmt)->closeCursor(); + } + } catch (\Exception $e) { + return false; + } + return true; + } + + /** + * Build and execute SQL queries necessary to drop the constraints for the + * fixture + * + * @param \Cake\Database\Connection $db An instance of the database into which the constraints will be dropped + * @return bool on success or if there are no constraints to drop, or false on failure + */ + public function dropConstraints(Connection $db) + { + if (empty($this->constraints)) { + return true; + } + + $sql = $this->_schema->dropConstraintSql($db); + + if (empty($sql)) { + return true; + } + + try { + foreach ($sql as $stmt) { + $db->execute($stmt)->closeCursor(); + } + } catch (\Exception $e) { + return false; + } + + foreach ($this->constraints as $name => $data) { + $this->_schema->dropConstraint($name); + } + return true; + } + /** * Converts the internal records into data used to generate a query. * diff --git a/tests/TestCase/TestSuite/FixtureManagerTest.php b/tests/TestCase/TestSuite/FixtureManagerTest.php index 5138ab7aef8..7440ec0ef36 100644 --- a/tests/TestCase/TestSuite/FixtureManagerTest.php +++ b/tests/TestCase/TestSuite/FixtureManagerTest.php @@ -16,6 +16,7 @@ use Cake\Core\Plugin; use Cake\Database\ConnectionManager; +use Cake\ORM\TableRegistry; use Cake\TestSuite\Fixture\FixtureManager; use Cake\TestSuite\TestCase; @@ -52,6 +53,63 @@ public function testFixturizeCore() $this->assertInstanceOf('Cake\Test\Fixture\ArticlesFixture', $fixtures['core.articles']); } + /** + * Test loading fixtures with constraints. + * + * @return void + */ + public function testFixturizeCoreConstraint() + { + $test = $this->getMock('Cake\TestSuite\TestCase'); + $test->fixtures = ['core.articles', 'core.articles_tags', 'core.tags']; + $this->manager->fixturize($test); + $this->manager->load($test); + + $table = TableRegistry::get('ArticlesTags'); + $schema = $table->schema(); + + $this->assertEquals(['primary', 'tag_id_fk'], $schema->constraints()); + + $expectedConstraint = [ + 'type' => 'foreign', + 'columns' => [ + 'tag_id' + ], + 'references' => [ + 'tags', + 'id' + ], + 'update' => 'cascade', + 'delete' => 'cascade', + 'length' => [] + ]; + $this->assertEquals($expectedConstraint, $schema->constraint('tag_id_fk')); + $this->manager->unload($test); + + $this->manager->load($test); + $table = TableRegistry::get('ArticlesTags'); + $schema = $table->schema(); + + $this->assertEquals(['primary', 'tag_id_fk'], $schema->constraints()); + $expectedConstraint = [ + 'type' => 'foreign', + 'columns' => [ + 'tag_id' + ], + 'references' => [ + 'tags', + 'id' + ], + 'update' => 'cascade', + 'delete' => 'cascade', + 'length' => [] + ]; + $this->assertEquals($expectedConstraint, $schema->constraint('tag_id_fk')); + + $this->manager->unload($test); + $this->manager->shutDown($test); + } + /** * Test loading app fixtures. *