From ca14b86baecce3a229b5dfbaac8ceac0d2ee9e2b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 16:08:59 +1000 Subject: [PATCH 1/4] add load missing --- .../Database/Eloquent/Collection.php | 38 +++++++++- .../EloquentCollectionLoadMissingTest.php | 71 +++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index e5c6c383addb..b89a1a092f91 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Eloquent; +use Closure; use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; use Illuminate\Contracts\Support\Arrayable; @@ -221,7 +222,7 @@ public function loadMissing($relations) $relations = func_get_args(); } - foreach ($relations as $key => $value) { + foreach ($this->prepareLoadMissingRelationships($relations) as $key => $value) { if (is_numeric($key)) { $key = $value; } @@ -248,6 +249,41 @@ public function loadMissing($relations) return $this; } + /** + * Prepare nested load missing relationships. + * + * @param array $relations + * @param string $prefix + * @return array + */ + protected function prepareLoadMissingRelationships($relations, $prefix = '') + { + $preparedRelationships = []; + + foreach ($relations as $key => $value) { + $fullKey = $prefix ? "$prefix.$key" : $key; + + // If the value is not an array, it must be a string or callable + // so we can use the relationship without any further processing + if (! is_array($value)) { + $preparedRelationships[$fullKey] = $value; + } else { + // Check the array has a depth of 1, if not, recursively prepare the relationships + if (array_values($value) === $value) { + foreach ($value as $subValue) { + $preparedRelationships["$fullKey.$subValue"] = null; + } + } else { + // At this point, we are working with a nested relation + // so we must recursively prepare the relationships + $preparedRelationships = array_merge($preparedRelationships, $this->prepareLoadMissingRelationships($value, $fullKey)); + } + } + } + + return $preparedRelationships; + } + /** * Load a relationship path if it is not already eager loaded. * diff --git a/tests/Integration/Database/EloquentCollectionLoadMissingTest.php b/tests/Integration/Database/EloquentCollectionLoadMissingTest.php index e95d7aca9b41..4f21913acb42 100644 --- a/tests/Integration/Database/EloquentCollectionLoadMissingTest.php +++ b/tests/Integration/Database/EloquentCollectionLoadMissingTest.php @@ -92,6 +92,77 @@ public function testLoadMissingWithClosure() $this->assertArrayNotHasKey('post_id', $posts[0]->comments[1]->parent->getAttributes()); } + public function testLoadMissingWithNestedArray() + { + $posts = Post::with('comments')->get(); + + DB::enableQueryLog(); + + $posts->loadMissing(['comments' => ['parent']]); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertTrue($posts[0]->comments[0]->relationLoaded('parent')); + } + + public function testLoadMissingWithNestedArrayWithClosure() + { + $posts = Post::with('comments')->get(); + + DB::enableQueryLog(); + + $posts->loadMissing(['comments' => ['parent' => function ($query) { + $query->select('id'); + }]]); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertTrue($posts[0]->comments[0]->relationLoaded('parent')); + $this->assertArrayNotHasKey('post_id', $posts[0]->comments[1]->parent->getAttributes()); + } + + public function testLoadMissingWithMultipleNestedArrays() + { + $users = User::get(); + $users->loadMissing([ + 'posts' => [ + 'postRelation' => [ + 'postSubRelations' => [ + 'postSubSubRelations', + ], + ], + ], + ]); + + $user = $users->first(); + $this->assertEquals(2, $user->posts->count()); + $this->assertNull($user->posts[0]->postRelation); + $this->assertInstanceOf(PostRelation::class, $user->posts[1]->postRelation); + $this->assertEquals(1, $user->posts[1]->postRelation->postSubRelations->count()); + $this->assertInstanceOf(PostSubRelation::class, $user->posts[1]->postRelation->postSubRelations[0]); + $this->assertEquals(1, $user->posts[1]->postRelation->postSubRelations[0]->postSubSubRelations->count()); + $this->assertInstanceOf(PostSubSubRelation::class, $user->posts[1]->postRelation->postSubRelations[0]->postSubSubRelations[0]); + } + + public function testLoadMissingWithMultipleNestedArraysCombinedWithDotNotation() + { + $users = User::get(); + $users->loadMissing([ + 'posts' => [ + 'postRelation' => [ + 'postSubRelations.postSubSubRelations' + ] + ] + ]); + + $user = $users->first(); + $this->assertEquals(2, $user->posts->count()); + $this->assertNull($user->posts[0]->postRelation); + $this->assertInstanceOf(PostRelation::class, $user->posts[1]->postRelation); + $this->assertEquals(1, $user->posts[1]->postRelation->postSubRelations->count()); + $this->assertInstanceOf(PostSubRelation::class, $user->posts[1]->postRelation->postSubRelations[0]); + $this->assertEquals(1, $user->posts[1]->postRelation->postSubRelations[0]->postSubSubRelations->count()); + $this->assertInstanceOf(PostSubSubRelation::class, $user->posts[1]->postRelation->postSubRelations[0]->postSubSubRelations[0]); + } + public function testLoadMissingWithDuplicateRelationName() { $posts = Post::with('comments')->get(); From 1ef2b1e930c9841ec80138f735e80b4f4d24488f Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 16:11:05 +1000 Subject: [PATCH 2/4] wording --- src/Illuminate/Database/Eloquent/Collection.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index b89a1a092f91..8a0800adb311 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -263,19 +263,18 @@ protected function prepareLoadMissingRelationships($relations, $prefix = '') foreach ($relations as $key => $value) { $fullKey = $prefix ? "$prefix.$key" : $key; - // If the value is not an array, it must be a string or callable - // so we can use the relationship without any further processing + // If the value is not an array, it can be used as-is if (! is_array($value)) { $preparedRelationships[$fullKey] = $value; } else { - // Check the array has a depth of 1, if not, recursively prepare the relationships if (array_values($value) === $value) { + // If the array has a depth of 1, we simply flatten the array foreach ($value as $subValue) { $preparedRelationships["$fullKey.$subValue"] = null; } } else { - // At this point, we are working with a nested relation - // so we must recursively prepare the relationships + // We now know that the remaining relationships are nested arrays + // so we must prepare them recursively $preparedRelationships = array_merge($preparedRelationships, $this->prepareLoadMissingRelationships($value, $fullKey)); } } From 3e9f58483c68925aab639f8b3130e651b0859eb5 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 16:19:46 +1000 Subject: [PATCH 3/4] style --- .../Database/EloquentCollectionLoadMissingTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Integration/Database/EloquentCollectionLoadMissingTest.php b/tests/Integration/Database/EloquentCollectionLoadMissingTest.php index 4f21913acb42..e418221f2c63 100644 --- a/tests/Integration/Database/EloquentCollectionLoadMissingTest.php +++ b/tests/Integration/Database/EloquentCollectionLoadMissingTest.php @@ -148,9 +148,9 @@ public function testLoadMissingWithMultipleNestedArraysCombinedWithDotNotation() $users->loadMissing([ 'posts' => [ 'postRelation' => [ - 'postSubRelations.postSubSubRelations' - ] - ] + 'postSubRelations.postSubSubRelations', + ], + ], ]); $user = $users->first(); From 47e7293bb84ce552d9b4df4dda0a48e65ddc7266 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Feb 2025 16:20:37 +1000 Subject: [PATCH 4/4] style --- src/Illuminate/Database/Eloquent/Collection.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 8a0800adb311..fba7eecd66f9 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Eloquent; -use Closure; use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; use Illuminate\Contracts\Support\Arrayable;