8000 [12.x] Support nested relations on `relationLoaded` method by tmsperera · Pull Request #55595 · laravel/framework · GitHub
[go: up one dir, main page]

Skip to content

[12.x] Support nested relations on relationLoaded method #55595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support nested relations on relationLoaded method
  • Loading branch information
tmsperera committed Apr 29, 2025
commit 05e1edfd6271e2316d43356ce5953cec22a9ba48
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ public function getRelationValue($key)
// If the key already exists in the relationships array, it just means the
// relationship has already been loaded, so we'll just return it out of
// here because there is no need to query within the relations twice.
if ($this->relationLoaded($key)) {
if (array_key_exists($key, $this->relations)) {
return $this->relations[$key];
}

Expand Down
27 changes: 25 additions & 2 deletions src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ protected function attemptToAutoloadRelation($key)

$this->invokeRelationAutoloadCallbackFor($key, []);

return $this->relationLoaded($key);
return array_key_exists($key, $this->relations);
}

/**
Expand Down Expand Up @@ -1081,7 +1081,30 @@ public function getRelation($relation)
*/
public function relationLoaded($key)
{
return array_key_exists($key, $this->relations);
if (! str_contains($key, '.')) {
return array_key_exists($key, $this->relations);
}

$relations = explode('.', $key, 2);
$childRelation = $relations[0];
$nestedRelation = $relations[1];

// A relation may contain a single Model or collection of Models.
// So we prepare for both scenarios.
$relationValue = $this->getRelationValue($childRelation);
$relationValues = is_iterable($relationValue) ? $relationValue : [$relationValue];

foreach ($relationValues as $related) {
if (! $related instanceof Model) {
return false;
}

if (! $related->relationLoaded($nestedRelation)) {
return false;
}
}

return true;
}

/**
Expand Down
229 changes: 229 additions & 0 deletions tests/Integration/Database/EloquentModelRelationLoadedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php

namespace Illuminate\Tests\Integration\Database\EloquentModelRelationLoadedTest;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Tests\Integration\Database\DatabaseTestCase;

class EloquentModelRelationLoadedTest extends DatabaseTestCase
{
protected function afterRefreshingDatabase()
{
Schema::create('ones', function (Blueprint $table) {
$table->increments('id');
});

Schema::create('twos', function (Blueprint $table) {
$table->increments('id');
$table->integer('one_id');
});

Schema::create('threes', function (Blueprint $table) {
$table->increments('id');
$table->integer('two_id');
$table->integer('one_id')->nullable();
});
}

public function testWhenRelationIsInvalid()
{
$one = One::query()->create();
$one->twos()->create();
$one->load('twos');

$this->assertFalse($one->relationLoaded(''));
$this->assertFalse($one->relationLoaded('.'));
$this->assertFalse($one->relationLoaded('null'));
$this->assertFalse($one->relationLoaded('invalid'));
}

public function testWhenNestedRelationIsInvalid()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();
$one->load('twos.threes');

$this->assertFalse($one->relationLoaded('twos.'));
$this->assertFalse($one->relationLoaded('twos.null'));
$this->assertFalse($one->relationLoaded('twos.invalid'));
}

public function testWhenRelationNotLoaded()
{
$one = One::query()->create();

$this->assertFalse($one->relationLoaded('twos'));
}

public function testWhenRelationLoaded()
{
$one = One::query()->create();
$one->twos()->create();
$one->load(['twos']);

$this->assertTrue($one->relationLoaded('twos'));
}

public function testWhenChildRelationIsNotLoaded()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();
$one->load('twos');

$this->assertTrue($one->relationLoaded('twos'));
$this->assertFalse($one->relationLoaded('twos.threes'));
}

public function testWhenChildRelationIsLoaded()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();
$one->load('twos.threes');

$this->assertTrue($one->relationLoaded('twos'));
$this->assertTrue($one->relationLoaded('twos.threes'));
}

public function testWhenChildRecursiveRelationIsLoaded()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create(['one_id' => $one->id]);
$one->load('twos.threes.one');

$this->assertTrue($one->relationLoaded('twos'));
$this->assertTrue($one->relationLoaded('twos.threes'));
$this->assertTrue($one->relationLoaded('twos.threes.one'));
}

public function testWhenParentRelationIsASingleInstance()
{
$one = One::query()->create();
$two = $one->twos()->create();
$three = $two->threes()->create();
$three->load('two.one');

$this->assertTrue($three->relationLoaded('two'));
$this->assertTrue($three->relationLoaded('two.one'));
}

public function testWhenSetRelationsWithValidData()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();

$one->setRelations([
'twos' => $one->twos()->with('threes')->get(),
]);

$this->assertTrue($one->relationLoaded('twos'));
$this->assertTrue($one->relationLoaded('twos.threes'));
$this->assertNull($one->getAttribute('twos.threes'));
}

public function testWhenGetNestedAttribute()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();
$one->load('twos.threes');

$this->assertNull($one->getAttribute('twos.threes'));
}

/**
* This is a regression test to ensure previous functionality remains intact.
*/
public function testWhenSetRelationsWithCustomData()
{
$one = One::query()->create();
$two = $one->twos()->create();
$three = $two->threes()->create();
$three->load('two.one');

$this->assertTrue($three->relationLoaded('two'));
$this->assertTrue($three->relationLoaded('two.one'));

$three->setRelations(['a' => ['x', 'y', 'z']]);

$this->assertFalse($three->relationLoaded('two'));
$this->assertFalse($three->relationLoaded('two.one'));
$this->assertNull($three->getAttribute('a.z'));
$this->assertTrue($three->relationLoaded('a'));
$this->assertIsArray($three->getRelationValue('a'));
}

public function testWhenSetRelationsWithDotsInRelationNames()
{
$one = One::query()->create();

$one->setRelations(['foo.bar' => ['x', 'y', 'z']]);

$this->assertNotNull($one->getRelationValue('foo.bar'));
}

public function testGetRelationValue()
{
$one = One::query()->create();
$two = $one->twos()->create();
$two->threes()->create();
$one->load('twos.threes');

$this->assertNotNull($one->getAttribute('twos'));
$this->assertNull($one->getRelationValue('twos.threes'));
}
}

class One extends Model
{
public $table = 'ones';
public $timestamps = false;
protected $guarded = [];

public function twos(): HasMany
{
return $this->hasMany(Two::class, 'one_id');
}
}

class Two extends Model
{
public $table = 'twos';
public $timestamps = false;
protected $guarded = [];

public function one(): BelongsTo
{
return $this->belongsTo(One::class, 'one_id');
}

public function threes(): HasMany
{
return $this->hasMany(Three::class, 'two_id');
}
}

class Three extends Model
{
public $table = 'threes';
public $timestamps = false;
protected $guarded = [];

public function one(): BelongsTo
{
return $this->belongsTo(One::class, 'one_id');
}

public function two(): BelongsTo
{
return $this->belongsTo(Two::class, 'two_id');
}
}
Loading
0