Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 33 additions & 0 deletions src/core/src/Database/Eloquent/Attributes/CollectedBy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Hypervel\Database\Eloquent\Attributes;

use Attribute;

/**
* Declare the collection class for a model using an attribute.
*
* When placed on a model class, the model will use the specified collection
* class when creating new collection instances via newCollection().
*
* @example
* ```php
* #[CollectedBy(CustomCollection::class)]
* class User extends Model {}
* ```
*/
#[Attribute(Attribute::TARGET_CLASS)]
class CollectedBy
{
/**
* Create a new attribute instance.
*
* @param class-string<\Hypervel\Database\Eloquent\Collection<array-key, \Hypervel\Database\Eloquent\Model>> $collectionClass
*/
public function __construct(
public string $collectionClass,
) {
}
}
2 changes: 1 addition & 1 deletion src/core/src/Database/Eloquent/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/**
* @template TKey of array-key
* @template TModel of \Hypervel\Database\Eloquent\Model
* @template TModel of \Hyperf\Database\Model\Model
*
* @extends \Hyperf\Database\Model\Collection<TKey, TModel>
*
Expand Down
60 changes: 60 additions & 0 deletions src/core/src/Database/Eloquent/Concerns/HasCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Hypervel\Database\Eloquent\Concerns;

use Hypervel\Database\Eloquent\Attributes\CollectedBy;
use Hypervel\Database\Eloquent\Collection;
use ReflectionClass;

/**
* Provides support for custom collection classes on models.
*
* Models can specify their collection class in two ways:
* 1. Using the #[CollectedBy] attribute (takes precedence)
* 2. Overriding the static $collectionClass property
*
* The fallback chain is: #[CollectedBy] attribute → $collectionClass property.
*/
trait HasCollection
{
/**
* The resolved collection class names by model.
*
* @var array<class-string, class-string<Collection>>
*/
protected static array $resolvedCollectionClasses = [];

/**
* Create a new Eloquent Collection instance.
*
* @param array<array-key, static> $models
* @return \Hypervel\Database\Eloquent\Collection<array-key, static>
*/
public function newCollection(array $models = []): Collection
{
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 null|class-string<Collection>
*/
protected function resolveCollectionFromAttribute(): ?string
{
$reflectionClass = new ReflectionClass(static::class);

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

if (! isset($attributes[0])) {
return null;
}

// @phpstan-ignore return.type (attribute stores generic Model type, but we know it's compatible with static)
return $attributes[0]->newInstance()->collectionClass;
}
}
21 changes: 12 additions & 9 deletions src/core/src/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Hypervel\Database\Eloquent\Concerns\HasAttributes;
use Hypervel\Database\Eloquent\Concerns\HasBootableTraits;
use Hypervel\Database\Eloquent\Concerns\HasCallbacks;
use Hypervel\Database\Eloquent\Concerns\HasCollection;
use Hypervel\Database\Eloquent\Concerns\HasGlobalScopes;
use Hypervel\Database\Eloquent\Concerns\HasLocalScopes;
use Hypervel\Database\Eloquent\Concerns\HasObservers;
Expand Down Expand Up @@ -73,13 +74,24 @@ abstract class Model extends BaseModel implements UrlRoutable, HasBroadcastChann
use HasAttributes;
use HasBootableTraits;
use HasCallbacks;
use HasCollection;
use HasGlobalScopes;
use HasLocalScopes;
use HasObservers;
use HasRelations;
use HasRelationships;
use TransformsToResource;

/**
* The default collection class for this model.
*
* Override this property to use a custom collection class. Alternatively,
* use the #[CollectedBy] attribute for a more declarative approach.
*
* @var class-string<Collection>
*/
protected static string $collectionClass = Collection::class;

protected ?string $connection = null;

public function resolveRouteBinding($value)
Expand All @@ -97,15 +109,6 @@ public function newModelBuilder($query)
return new Builder($query);
}

/**
* @param array<array-key, static> $models
* @return \Hypervel\Database\Eloquent\Collection<array-key, static>
*/
public function newCollection(array $models = [])
{
return new Collection($models);
}

public function broadcastChannelRoute(): string
{
return str_replace('\\', '.', get_class($this)) . '.{' . Str::camel(class_basename($this)) . '}';
Expand Down
13 changes: 13 additions & 0 deletions src/core/src/Database/Eloquent/Relations/MorphPivot.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Hypervel\Database\Eloquent\Relations;

use Hyperf\DbConnection\Model\Relations\MorphPivot as BaseMorphPivot;
use Hypervel\Database\Eloquent\Collection;
use Hypervel\Database\Eloquent\Concerns\HasAttributes;
use Hypervel\Database\Eloquent\Concerns\HasCallbacks;
use Hypervel\Database\Eloquent\Concerns\HasCollection;
use Hypervel\Database\Eloquent\Concerns\HasGlobalScopes;
use Hypervel\Database\Eloquent\Concerns\HasObservers;
use Psr\EventDispatcher\StoppableEventInterface;
Expand All @@ -15,9 +17,20 @@ class MorphPivot extends BaseMorphPivot
{
use HasAttributes;
use HasCallbacks;
use HasCollection;
use HasGlobalScopes;
use HasObservers;

/**
* The default collection class for this model.
*
* Override this property to use a custom collection class. Alternatively,
* use the #[CollectedBy] attribute for a more declarative approach.
*
* @var class-string<Collection>
*/
protected static string $collectionClass = Collection::class;

/**
* Delete the pivot model record from the database.
*
Expand Down
13 changes: 13 additions & 0 deletions src/core/src/Database/Eloquent/Relations/Pivot.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Hypervel\Database\Eloquent\Relations;

use Hyperf\DbConnection\Model\Relations\Pivot as BasePivot;
use Hypervel\Database\Eloquent\Collection;
use Hypervel\Database\Eloquent\Concerns\HasAttributes;
use Hypervel\Database\Eloquent\Concerns\HasCallbacks;
use Hypervel\Database\Eloquent\Concerns\HasCollection;
use Hypervel\Database\Eloquent\Concerns\HasGlobalScopes;
use Hypervel\Database\Eloquent\Concerns\HasObservers;
use Psr\EventDispatcher\StoppableEventInterface;
Expand All @@ -15,9 +17,20 @@ class Pivot extends BasePivot
{
use HasAttributes;
use HasCallbacks;
use HasCollection;
use HasGlobalScopes;
use HasObservers;

/**
* The default collection class for this model.
*
* Override this property to use a custom collection class. Alternatively,
* use the #[CollectedBy] attribute for a more declarative approach.
*
* @var class-string<Collection>
*/
protected static string $collectionClass = Collection::class;

/**
* Delete the pivot model record from the database.
*
Expand Down
Loading