Skip to content

Commit

Permalink
[11.x] Add CollectedBy attribute (#53122)
Browse files Browse the repository at this point in the history
* Add CollectedBy attribute

* Fix styling

* Fix null checks

* Fix styling

* Fix type checks

* Wrap collection class name resolution in once

* Fix docblock return type

Co-authored-by: Jeffrey Angenent <[email protected]>

* Use static variable instead of once function to memoize

* formatting

---------

Co-authored-by: Jeffrey Angenent <[email protected]>
Co-authored-by: Taylor Otwell <[email protected]>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent ddfbbf0 commit 04e2e19
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 2 deletions.
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Eloquent/Attributes/CollectedBy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Illuminate\Database\Eloquent\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class CollectedBy
{
/**
* Create a new attribute instance.
*
* @param class-string<\Illuminate\Database\Eloquent\Collection<*, *>> $collectionClass
* @return void
*/
public function __construct(public string $collectionClass)
{
}
}
32 changes: 31 additions & 1 deletion src/Illuminate/Database/Eloquent/HasCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@

namespace Illuminate\Database\Eloquent;

use Illuminate\Database\Eloquent\Attributes\CollectedBy;
use ReflectionClass;

/**
* @template TCollection of \Illuminate\Database\Eloquent\Collection
*/
trait HasCollection
{
/**
* The resolved collection class names by model.
*
* @var array<class-string<static>, class-string<TCollection>>
*/
protected static array $resolvedCollectionClasses = [];

/**
* Create a new Eloquent Collection instance.
*
Expand All @@ -15,6 +25,26 @@ trait HasCollection
*/
public function newCollection(array $models = [])
{
return new static::$collectionClass($models);
static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? static::$collectionClass);

return new static::$resolvedCollectionClasses[static::class]($models);
}

/**
* Resolve the collection class name from the CollectedBy attribute.
*
* @return class-string<TCollection>|null
*/
public function resolveCollectionFromAttribute()
{
$reflectionClass = new ReflectionClass(static::class);

$attributes = $reflectionClass->getAttributes(CollectedBy::class);

if (! isset($attributes[0]) || ! isset($attributes[0]->getArguments()[0])) {
return;
}

return $attributes[0]->getArguments()[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ protected function getRelation()
$builder = m::mock(Builder::class);
$related = m::mock(Model::class);
$related->shouldReceive('newCollection')->passthru();
$related->shouldReceive('resolveCollectionFromAttribute')->passthru();
$builder->shouldReceive('getModel')->andReturn($related);
$related->shouldReceive('qualifyColumn');
$builder->shouldReceive('join', 'where');
Expand Down
18 changes: 18 additions & 0 deletions tests/Database/DatabaseEloquentModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\ArrayObject;
Expand Down Expand Up @@ -3169,6 +3170,14 @@ public function testGuardedWithMutators()
$this->assertSame('123 Main Street', $model->address_line_one);
$this->assertSame('Anytown', $model->address_line_two);
}

public function testCollectedByAttribute()
{
$model = new EloquentModelWithCollectedByAttribute;
$collection = $model->newCollection([$model]);

$this->assertInstanceOf(CustomEloquentCollection::class, $collection);
}
}

class EloquentTestObserverStub
Expand Down Expand Up @@ -3928,3 +3937,12 @@ public function setFullAddressAttribute($fullAddress)
$this->attributes['address_line_two'] = $addressLineTwo;
}
}

#[CollectedBy(CustomEloquentCollection::class)]
class EloquentModelWithCollectedByAttribute extends Model
{
}

class CustomEloquentCollection extends Collection
{
}
20 changes: 19 additions & 1 deletion types/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace Illuminate\Types\Model;

use Illuminate\Database\Eloquent\Attributes\CollectedBy;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\HasCollection;
use Illuminate\Database\Eloquent\Model;
use User;

use function PHPStan\Testing\assertType;

function test(User $user, Post $post, Comment $comment): void
function test(User $user, Post $post, Comment $comment, Article $article): void
{
assertType('UserFactory', User::factory(function ($attributes, $model) {
assertType('array<string, mixed>', $attributes);
Expand All @@ -34,6 +35,7 @@ function test(User $user, Post $post, Comment $comment): void

assertType('Illuminate\Database\Eloquent\Collection<(int|string), User>', $user->newCollection([new User()]));
assertType('Illuminate\Types\Model\Posts<(int|string), Illuminate\Types\Model\Post>', $post->newCollection(['foo' => new Post()]));
assertType('Illuminate\Types\Model\Articles<(int|string), Illuminate\Types\Model\Article>', $article->newCollection([new Article()]));
assertType('Illuminate\Types\Model\Comments', $comment->newCollection([new Comment()]));

assertType('bool', $user->restore());
Expand Down Expand Up @@ -74,3 +76,19 @@ public function newCollection(array $models = []): Comments
final class Comments extends Collection
{
}

#[CollectedBy(Articles::class)]
class Article extends Model
{
/** @use HasCollection<Articles<array-key, static>> */
use HasCollection;
}

/**
* @template TKey of array-key
* @template TModel of Article
*
* @extends Collection<TKey, TModel> */
class Articles extends Collection
{
}

0 comments on commit 04e2e19

Please sign in to comment.