Browse Source

Allow preserving keys when using select loaders.

Setting the `preserveKeys` option for the association's finder query now
preserves the record keys when populating the associated records array for
the parent records.

Refs #10118
ADmad 1 year ago
parent
commit
514b828dee

+ 12 - 3
src/ORM/Association/Loader/SelectLoader.php

@@ -485,16 +485,25 @@ class SelectLoader
             $this->bindingKey;
         $key = (array)$keys;
 
-        foreach ($fetchQuery->all() as $result) {
+        $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false;
+
+        foreach ($fetchQuery->all() as $i => $result) {
             $values = [];
             foreach ($key as $k) {
                 $values[] = $result[$k];
             }
+
             if ($singleResult) {
                 $resultMap[implode(';', $values)] = $result;
-            } else {
-                $resultMap[implode(';', $values)][] = $result;
+                continue;
             }
+
+            if ($preserveKeys) {
+                $resultMap[implode(';', $values)][$i] = $result;
+                continue;
+            }
+
+            $resultMap[implode(';', $values)][] = $result;
         }
 
         return $resultMap;

+ 8 - 1
src/ORM/Association/Loader/SelectWithPivotLoader.php

@@ -178,8 +178,9 @@ class SelectWithPivotLoader extends SelectLoader
     {
         $resultMap = [];
         $key = (array)$options['foreignKey'];
+        $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false;
 
-        foreach ($fetchQuery->all() as $result) {
+        foreach ($fetchQuery->all() as $i => $result) {
             if (!isset($result[$this->junctionProperty])) {
                 throw new DatabaseException(sprintf(
                     '`%s` is missing from the belongsToMany results. Results cannot be created.',
@@ -191,6 +192,12 @@ class SelectWithPivotLoader extends SelectLoader
             foreach ($key as $k) {
                 $values[] = $result[$this->junctionProperty][$k];
             }
+
+            if ($preserveKeys) {
+                $resultMap[implode(';', $values)][$i] = $result;
+                continue;
+            }
+
             $resultMap[implode(';', $values)][] = $result;
         }
 

+ 33 - 0
tests/TestCase/ORM/Query/SelectQueryTest.php

@@ -47,6 +47,7 @@ use PHPUnit\Framework\Attributes\DataProvider;
 use ReflectionProperty;
 use TestApp\Model\Table\ArticlesTable;
 use TestApp\Model\Table\AuthorsTable;
+use TestApp\Model\Table\TagsTable;
 
 /**
  * Tests SelectQuery class
@@ -1432,6 +1433,24 @@ class SelectQueryTest extends TestCase
         $this->assertInstanceOf(DateTime::class, $first->tags[0]->created);
     }
 
+    public function testBelongsToManyWithPreservedKeys(): void
+    {
+        $table = $this->getTableLocator()->get('Articles');
+        $this->getTableLocator()->get('Tags', ['className' => TagsTable::class]);
+        $table->belongsToMany('Tags');
+
+        $first = $table->find()
+            ->where(['Articles.id' => 1])
+            ->contain([
+                'Tags' => ['finder' => 'slugged'],
+            ])
+            ->first();
+
+        $this->assertArrayHasKey('tag1', $first->tags);
+        $this->assertArrayHasKey('tag2', $first->tags);
+        $this->assertSame('tag1', $first->tags['tag1']->name);
+    }
+
     /**
      * Tests that belongsTo relations are correctly hydrated
      */
@@ -3028,6 +3047,18 @@ class SelectQueryTest extends TestCase
                 ],
             ]);
 
+        $resultWithSlugIndexedArticles = $table->find('all')
+            ->where(['id' => 1])
+            ->contain([
+                'Articles' => [
+                    'finder' => [
+                        'slugged' => [
+                            'preserveKeys' => true,
+                        ],
+                    ],
+                ],
+            ]);
+
         $this->assertCount(2, $resultWithArticles->first()->articles);
         $this->assertCount(2, $resultWithArticlesArray->first()->articles);
 
@@ -3038,6 +3069,8 @@ class SelectQueryTest extends TestCase
         );
 
         $this->assertCount(0, $resultWithoutArticles->first()->articles);
+
+        $this->assertSame('First-Article', key($resultWithSlugIndexedArticles->first()->articles));
     }
 
     /**

+ 10 - 0
tests/test_app/TestApp/Model/Table/ArticlesTable.php

@@ -16,6 +16,7 @@ namespace TestApp\Model\Table;
 
 use Cake\ORM\Query\SelectQuery;
 use Cake\ORM\Table;
+use Cake\Utility\Text;
 
 /**
  * Article table class
@@ -69,6 +70,15 @@ class ArticlesTable extends Table
         return $query;
     }
 
+    public function findSlugged(SelectQuery $query): SelectQuery
+    {
+        return $query->formatResults(function ($results) {
+            return $results->indexBy(function ($row) {
+                return Text::slug($row['title']);
+            });
+        });
+    }
+
     /**
      * Example public method
      */

+ 12 - 0
tests/test_app/TestApp/Model/Table/TagsTable.php

@@ -14,7 +14,9 @@ declare(strict_types=1);
  */
 namespace TestApp\Model\Table;
 
+use Cake\ORM\Query\SelectQuery;
 use Cake\ORM\Table;
+use Cake\Utility\Text;
 
 /**
  * Tag table class
@@ -27,4 +29,14 @@ class TagsTable extends Table
         $this->belongsToMany('Articles');
         $this->hasMany('ArticlesTags', ['propertyName' => 'extraInfo']);
     }
+
+    public function findSlugged(SelectQuery $query): SelectQuery
+    {
+        return $query->applyOptions(['preserveKeys' => true])
+            ->formatResults(function ($results) {
+                return $results->indexBy(function ($record) {
+                    return Text::slug($record->name);
+                });
+            });
+    }
 }