Skip to content

Commit

Permalink
Add Collection::loadMissing()
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed May 9, 2018
1 parent 1fc6cd5 commit 27b1652
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 4 deletions.
58 changes: 57 additions & 1 deletion src/Illuminate/Database/Eloquent/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function find($key, $default = null)
/**
* Load a set of relationships onto the collection.
*
* @param mixed $relations
* @param array|string $relations
* @return $this
*/
public function load($relations)
Expand Down Expand Up @@ -86,6 +86,62 @@ public function loadMorph($relation, $relations)
return $this;
}

/**
* Load a set of relationships onto the collection if they are not already eager loaded.
*
* @param array|string $relations
* @return $this
*/
public function loadMissing($relations)
{
if (is_string($relations)) {
$relations = func_get_args();
}

foreach ($relations as $relation) {
$this->loadMissingRelation($this, explode('.', $relation));
}

return $this;
}

/**
* Load a relationship path if it is not already eager loaded.
*
* @param \Illuminate\Database\Eloquent\Collection $models
* @param array $path
* @return void
*/
protected function loadMissingRelation(Collection $models, array $path)
{
// Get the first relationship.
$relation = array_shift($path);

// Handle relationships with specific columns.
$name = explode(':', $relation)[0];

// Load the relationship where missing.
$models->filter(function ($model) use ($name) {
return ! is_null($model) && ! $model->relationLoaded($name);
})->load($relation);

// End the recursion.
if (empty($path)) {
return;
}

// Get the models for the next level.
$models = $models->pluck($name);

// Handle *-many relationships.
if ($models->first() instanceof BaseCollection) {
$models = $models->collapse();
};

// Load the remaining path.
$this->loadMissingRelation(new static($models), $path);
}

/**
* Add an item to the collection.
*
Expand Down
6 changes: 3 additions & 3 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,9 @@ public function loadMissing($relations)
{
$relations = is_string($relations) ? func_get_args() : $relations;

return $this->load(array_filter($relations, function ($relation) {
return ! $this->relationLoaded($relation);
}));
$this->newCollection([$this])->loadMissing($relations);

return $this;
}

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

namespace Illuminate\Tests\Integration\Database\EloquentCollectionLoadMissingTest;

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

/**
* @group integration
*/
class EloquentCollectionLoadMissingTest extends DatabaseTestCase
{
public function setUp()
{
parent::setUp();

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

Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
});

Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('parent_id')->nullable();
$table->unsignedInteger('post_id');
});

Schema::create('revisions', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('comment_id');
});

User::create();

Post::create(['user_id' => 1]);

Comment::create(['parent_id' => null, 'post_id' => 1]);
Comment::create(['parent_id' => 1, 'post_id' => 1]);

Revision::create(['comment_id' => 1]);
}

public function testLoadMissing()
{
$posts = Post::with('comments', 'user')->get();

\DB::enableQueryLog();

$posts->loadMissing('comments.parent:id.revisions', 'user:id');

$this->assertCount(2, \DB::getQueryLog());
$this->assertTrue($posts[0]->comments[0]->relationLoaded('parent'));
$this->assertTrue($posts[0]->comments[1]->parent->relationLoaded('revisions'));
$this->assertFalse(array_key_exists('post_id', $posts[0]->comments[1]->parent->getAttributes()));
}
}

class Comment extends Model
{
public $timestamps = false;

protected $guarded = ['id'];

public function parent() {
return $this->belongsTo(Comment::class);
}

public function revisions() {
return $this->hasMany(Revision::class);
}
}

class Post extends Model
{
public $timestamps = false;

protected $guarded = ['id'];

public function comments() {
return $this->hasMany(Comment::class);
}

public function user() {
return $this->belongsTo(User::class);
}
}

class Revision extends Model
{
public $timestamps = false;

protected $guarded = ['id'];
}

class User extends Model
{
public $timestamps = false;
}
66 changes: 66 additions & 0 deletions tests/Integration/Database/EloquentModelLoadMissingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Illuminate\Tests\Integration\Database\EloquentModelLoadMissingTest;

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

/**
* @group integration
*/
class EloquentModelLoadMissingTest extends DatabaseTestCase
{
public function setUp()
{
parent::setUp();

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

Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('parent_id')->nullable();
$table->unsignedInteger('post_id');
});

Post::create();

Comment::create(['parent_id' => null, 'post_id' => 1]);
Comment::create(['parent_id' => 1, 'post_id' => 1]);
}

public function testLoadMissing()
{
$post = Post::with('comments')->first();

\DB::enableQueryLog();

$post->loadMissing('comments.parent');

$this->assertCount(1, \DB::getQueryLog());
$this->assertTrue($post->comments[0]->relationLoaded('parent'));
}
}

class Comment extends Model
{
public $timestamps = false;

protected $guarded = ['id'];

public function parent() {
return $this->belongsTo(Comment::class);
}
}

class Post extends Model
{
public $timestamps = false;

public function comments() {
return $this->hasMany(Comment::class);
}
}

0 comments on commit 27b1652

Please sign in to comment.