Skip to content

[12.x] Adds EagerLoad attribute and initializeOnQueue hook #55590

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

Draft
wants to merge 19 commits into
base: 12.x
Choose a base branch
from
Draft
17 changes: 17 additions & 0 deletions src/Illuminate/Queue/Attributes/EagerLoad.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Illuminate\Queue\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class EagerLoad
{
/**
* @param list<string> $relations
*/
public function __construct(public array $relations)
{
//
}
}
23 changes: 20 additions & 3 deletions src/Illuminate/Queue/SerializesModels.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace Illuminate\Queue;

use Illuminate\Bus\Queueable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\Attributes\EagerLoad;
use Illuminate\Queue\Attributes\WithoutRelations;
use ReflectionClass;
use ReflectionProperty;
Expand Down Expand Up @@ -89,9 +93,22 @@ public function __unserialize(array $values)
continue;
}

$property->setValue(
$this, $this->getRestoredPropertyValue($values[$name])
);
$value = $this->getRestoredPropertyValue($values[$name]);

$property->setValue($this, $value);

if (
($value instanceof Model || $value instanceof Collection) &&
($attribute = $property->getAttributes(EagerLoad::class)[0] ?? null)
) {
$relations = $attribute->getArguments()[0];

$value->load($relations);
}
}

if (in_array(Queueable::class, class_uses_recursive($this)) && method_exists($this, 'initializeOnQueue')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like swapping the order would be more optimal, but probably not by much:

if (method_exists($this, 'initializeOnQueue') && in_array(Queueable::class, class_uses_recursive($this))) {

Probably would only be important if this was called a lot.

$this->initializeOnQueue();
}
}

Expand Down
150 changes: 150 additions & 0 deletions tests/Integration/Queue/ModelSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Queue\Attributes\EagerLoad;
use Illuminate\Queue\Attributes\WithoutRelations;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use LogicException;
use Orchestra\Testbench\Attributes\WithConfig;
use Orchestra\Testbench\TestCase;
Expand Down Expand Up @@ -374,6 +378,109 @@ public function test_serialization_types_empty_custom_eloquent_collection()

$this->assertTrue(true);
}

public function test_it_can_eagerly_load_relationships_on_models()
{
$user = User::create([
'email' => 'taylor@laravel.com',
]);

$serialize = serialize(new ModelSerializationEagerLoadRoles($user));

$queries = [];
DB::listen(function (QueryExecuted $query) use (&$queries) {
$queries[] = $query;
});

$unserialized = unserialize($serialize);

$this->assertFalse($user->relationLoaded('roles'));
$this->assertTrue($unserialized->value->relationLoaded('roles'));
$this->assertCount(2, $queries);
}

public function test_it_can_eagerly_load_relationships_on_collections()
{
$users = new Collection([
User::create(['email' => 'taylor@laravel.com']),
User::create(['email' => 'tim@laravel.com']),
]);

$serialize = serialize(new ModelSerializationEagerLoadRoles($users));

$queries = [];
DB::listen(function (QueryExecuted $query) use (&$queries) {
$queries[] = $query;
});

$unserialized = unserialize($serialize);

$this->assertTrue($unserialized->value->every->relationLoaded('roles'));
$this->assertFalse($users->some->relationLoaded('roles'));
$this->assertCount(2, $queries);
}

public function test_it_eager_loads_already_loaded_relationships()
{
$user = User::create([
'email' => 'taylor@laravel.com',
]);
$user->roles()->create();
$user->load('roles');

$serialized = serialize(new ModelSerializationEagerLoadRoles($user));

// create a role while the value is serialized...
$user->roles()->create();

$queries = [];
DB::listen(function (QueryExecuted $query) use (&$queries) {
$queries[] = $query;
});
$unserialized = unserialize($serialized);

$this->assertTrue($unserialized->value->relationLoaded('roles'));
$this->assertTrue($user->relationLoaded('roles'));
$this->assertCount(2, $unserialized->value->roles);
$this->assertCount(1, $user->roles);
$this->assertCount(3, $queries);
}

public function test_it_can_mix_without_relations_and_eager_load()
{
$order = Order::create()->load('products');

$serialize = serialize(new ModelSerializationWithoutRelationsAndEagerLoadLines($order));

$queries = [];
DB::listen(function (QueryExecuted $query) use (&$queries) {
$queries[] = $query;
});

$unserialized = unserialize($serialize);

$this->assertTrue($unserialized->value->relationLoaded('lines'));
$this->assertTrue(! $unserialized->value->relationLoaded('products'));
$this->assertCount(2, $queries);
}

public function test_it_initilize_on_the_queue()
{
$order = Order::create();

$serialized = serialize(new ModelSerializationWithInitilizeOnQueueHook($order));

$queries = [];
DB::listen(function (QueryExecuted $query) use (&$queries) {
$queries[] = $query;
});

$unserialized = unserialize($serialized);

$this->assertTrue($unserialized->value->relationLoaded('lines'));
$this->assertTrue(! $unserialized->value->relationLoaded('products'));
$this->assertCount(2, $queries);
}
}

trait TraitBootsAndInitializersTest
Expand Down Expand Up @@ -567,6 +674,49 @@ public function __construct(public User $user, public DataValueObject $value)
}
}

class ModelSerializationEagerLoadRoles
{
use SerializesModels;

public function __construct(
#[EagerLoad(['roles'])]
public $value,
) {
//
}
}

#[WithoutRelations]
class ModelSerializationWithoutRelationsAndEagerLoadLines
{
use SerializesModels;

public function __construct(
#[EagerLoad(['lines'])]
public $value
) {
//
}
}

class ModelSerializationWithInitilizeOnQueueHook
{
use Queueable;

public function __construct(
public $value,
) {
//
}

protected function initializeOnQueue()
{
$this->value->load([
'lines' => fn ($q) => $q->where('product_id', 5),
]);
}
}

class ModelRelationSerializationTestClass
{
use SerializesModels;
Expand Down