diff --git a/src/ORM/Association/BelongsToMany.php b/src/ORM/Association/BelongsToMany.php index ca3d03837c4..1671530fdaa 100644 --- a/src/ORM/Association/BelongsToMany.php +++ b/src/ORM/Association/BelongsToMany.php @@ -543,11 +543,17 @@ protected function _saveLinks(EntityInterface $sourceEntity, $targetEntities, $o $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionAlias]); } - $joint->set(array_combine( - $foreignKey, - $sourceEntity->extract($bindingKey) - ), ['guard' => false]); - $joint->set(array_combine($assocForeignKey, $e->extract($targetPrimaryKey)), ['guard' => false]); + $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); + $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey)); + + if ($sourceKeys !== $joint->extract($foreignKey)) { + $joint->set($sourceKeys, ['guard' => false]); + } + + if ($targetKeys !== $joint->extract($assocForeignKey)) { + $joint->set($targetKeys, ['guard' => false]); + } + $saved = $junction->save($joint, $options); if (!$saved && !empty($options['atomic'])) { diff --git a/tests/TestCase/ORM/TableTest.php b/tests/TestCase/ORM/TableTest.php index 08a9288bc42..3ef0a5a4c7f 100644 --- a/tests/TestCase/ORM/TableTest.php +++ b/tests/TestCase/ORM/TableTest.php @@ -4382,6 +4382,43 @@ public function testSaveHasManyNoWasteSave() $this->assertEquals(1, $counter); } + /** + * Tests that on second save, entities for the belongsToMany relation are not marked + * as dirty unnecessarily. This helps avoid wasteful database statements and makes + * for a cleaner transaction log + * + * @return void + */ + public function testSaveBelongsToManyNoWasteSave() + { + $data = [ + 'title' => 'foo', + 'body' => 'bar', + 'tags' => [ + '_ids' => [1, 2] + ] + ]; + + $table = TableRegistry::get('Articles'); + $table->belongsToMany('Tags'); + $article = $table->save($table->newEntity($data, ['associated' => ['Tags']])); + + $counter = 0; + $table->Tags->junction() + ->eventManager() + ->on('Model.afterSave', function ($event, $entity) use (&$counter) { + if ($entity->dirty()) { + $counter++; + } + }); + + $article->tags[] = $table->Tags->get(3); + $this->assertCount(3, $article->tags); + $article->dirty('tags', true); + $table->save($article); + $this->assertEquals(1, $counter); + } + /** * Tests that after saving then entity contains the right primary * key casted to the right type