Browse Source

Cascade delete also triggered when foreign key does not accept nullvalues

Luan Hospodarsky 10 years ago
parent
commit
85a2e8c84f
2 changed files with 79 additions and 10 deletions
  1. 24 2
      src/ORM/Association/HasMany.php
  2. 55 8
      tests/TestCase/ORM/TableTest.php

+ 24 - 2
src/ORM/Association/HasMany.php

@@ -185,7 +185,7 @@ class HasMany extends Association
     }
 
     /**
-     * Deletes/sets null the related objects according to the dependency between source and targets
+     * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability
      *
      * @param array $properties array of foreignKey properties
      * @param EntityInterface $entity the entity which should have its associated entities unassigned
@@ -195,7 +195,10 @@ class HasMany extends Association
      */
     protected function _unlinkAssociated(array $properties, EntityInterface $entity, Table $target, array $options)
     {
-        if ($this->dependent()) {
+        $mustBeDependent = (!$this->_foreignKeyAcceptsNull($target, $properties) || $this->dependent());
+        $this->dependent($mustBeDependent);
+
+        if ($mustBeDependent) {
                 $this->cascadeDelete($entity, $options);
         } else {
             $updateFields = array_fill_keys(array_keys($properties), null);
@@ -204,6 +207,25 @@ class HasMany extends Association
     }
 
     /**
+     * Checks the nullable flag of the foreign key
+     *
+     * @param Table $table the table containing the foreign key
+     * @param array $properties the list of fields that compose the foreign key
+     * @return bool
+     */
+    protected function _foreignKeyAcceptsNull(Table $table, array $properties)
+    {
+        return array_product(
+            array_map(
+                function ($prop) use ($table) {
+                    return $table->schema()->isNullable($prop);
+                },
+                array_keys($properties)
+            )
+        );
+    }
+
+    /**
      * {@inheritDoc}
      */
     protected function _linkField($options)

+ 55 - 8
tests/TestCase/ORM/TableTest.php

@@ -1829,15 +1829,12 @@ class TableTest extends TestCase
      */
     public function testSaveDefaultSaveStrategy()
     {
-        $authors = $this->getMock(
-            'Cake\ORM\Table',
-            ['exists'],
+        $authors = new Table(
             [
-                [
-                    'connection' => $this->connection,
-                    'alias' => 'Authors',
-                    'table' => 'authors',
-                ]
+                'table' => 'authors',
+                'alias' => 'Authors',
+                'connection' => $this->connection,
+                'entityClass' => 'Cake\ORM\Entity',
             ]
         );
         $authors->hasMany('Articles', ['saveStrategy' => 'append']);
@@ -1887,6 +1884,56 @@ class TableTest extends TestCase
     }
 
     /**
+     * Test that the associated entities are unlinked and deleted when they have a not nullable foreign key
+     *
+     * @return void
+     */
+    public function testSaveReplaceSaveStrategyNotNullable()
+    {
+        $articles = $this->getMock(
+            'Cake\ORM\Table',
+            ['exists'],
+            [
+                [
+                    'connection' => $this->connection,
+                    'alias' => 'Articles',
+                    'table' => 'articles',
+                ]
+            ]
+        );
+
+        $articles->hasMany('Comments', ['saveStrategy' => 'replace']);
+
+        $article = $articles->newEntity([
+            'title' => 'Bakeries are sky rocketing',
+            'body' => 'All because of cake',
+            'comments' => [
+                [
+                    'user_id' => 1,
+                    'comment' => 'That is true!'
+                ],
+                [
+                    'user_id' => 2,
+                    'comment' => 'Of course'
+                ]
+            ]
+        ], ['associated' => ['Comments']]);
+
+        $article = $articles->save($article, ['associated' => ['Comments']]);
+        $commentId = $article->comments[0]->id;
+
+        $this->assertEquals(2, $articles->Comments->find('all')->where(['article_id' => $article->id])->count());
+        $this->assertTrue($articles->Comments->exists(['id' => $commentId]));
+        
+        unset($article->comments[0]);
+        $article->dirty('comments', true);
+        $article = $articles->save($article, ['associated' => ['Comments']]);
+
+        $this->assertEquals(1, $articles->Comments->find('all')->where(['article_id' => $article->id])->count());
+        $this->assertFalse($articles->Comments->exists(['id' => $commentId]));
+    }
+
+    /**
      * Test that saving a new entity with a Primary Key set does not call exists when checkExisting is false.
      *
      * @group save