Skip to content

Conversation

@binaryfire
Copy link
Contributor

This PR ports Laravel's #[CollectedBy] attribute to Hypervel, allowing models to declaratively specify a custom collection class.

Summary

  • Adds #[CollectedBy] attribute for declaring custom collection classes on models
  • Adds HasCollection trait providing the newCollection() method with attribute resolution
  • Modifies Model to use the new HasCollection trait

Usage

use Hypervel\Database\Eloquent\Attributes\CollectedBy;
use Hypervel\Database\Eloquent\Model;

#[CollectedBy(CustomUserCollection::class)]
class User extends Model
{
    // ...
}

// Now User::all() returns a CustomUserCollection
$users = User::all(); // instanceof CustomUserCollection

Changes

New Files

  • src/core/src/Database/Eloquent/Attributes/CollectedBy.php - The attribute class
  • src/core/src/Database/Eloquent/Concerns/HasCollection.php - Trait providing newCollection() with attribute resolution
  • tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php - Tests (9 tests, 16 assertions)

Modified Files

  • src/core/src/Database/Eloquent/Model.php - Added HasCollection trait, removed inline newCollection() method

How It Works

  1. When newCollection() is called on a model, the HasCollection trait checks for a #[CollectedBy] attribute
  2. If found, the specified collection class is used
  3. If not found, falls back to the default Hypervel\Database\Eloquent\Collection
  4. Collection classes are cached per model class for performance

Notes

  • PHP attributes are not inherited - child classes need their own #[CollectedBy] attribute if they want a custom collection
  • This follows Laravel's implementation pattern

Ports Laravel's #[CollectedBy] attribute to Hypervel, allowing models to
declaratively specify a custom collection class.

- Add CollectedBy attribute class
- Add HasCollection trait with newCollection() and attribute resolution
- Modify Model to use HasCollection trait
- Add tests (9 tests, 16 assertions)
@albertcht albertcht added the feature New feature or request label Jan 6, 2026
Comment on lines +50 to +55
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;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @albertcht. The difference is intentional. Laravel's approach silently falls back to the default collection if someone writes #[CollectedBy] without an argument (the ! isset($attributes[0]->getArguments()[0]) guard). But that's a bug in user code - the attribute has a required parameter.

Using newInstance()->collectionClass means PHP throws ArgumentCountError if the attribute is malformed, instead of silently falling back. That feels like the right approach here - the developer sees their mistake and can fix it.

*/
public function newCollection(array $models = []): Collection
{
static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? Collection::class);
Copy link
Member

Choose a reason for hiding this comment

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

Hi @binaryfire , what do you think if we support customizing collection type via static::$collectionClass property as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@albertcht Done

…attribute

# Conflicts:
#	src/core/src/Database/Eloquent/Model.php
Adds a static $collectionClass property to Model as an alternative to
the #[CollectedBy] attribute for specifying custom collection classes.

- Add protected static $collectionClass property to Model with default Collection::class
- Update HasCollection trait to fall back to static::$collectionClass
- Fallback chain: #[CollectedBy] attribute → $collectionClass property
- Add tests for property-based customization and attribute precedence
Extends collection customization support to pivot models, matching Laravel's
behavior where Pivot inherits collection functionality from Model.

Changes:
- Add HasCollection trait and $collectionClass property to Pivot
- Add HasCollection trait and $collectionClass property to MorphPivot
- Loosen Collection's TModel constraint from Hypervel\Model to Hyperf\Model
  (allows Collection to work with any Hyperf-based model including pivots)
- Simplify generic annotations in HasCollection for broader compatibility
- Add PivotCollectionTest with 9 tests covering both Pivot and MorphPivot

Pivot models can now use #[CollectedBy] attribute or override $collectionClass
to specify custom collection classes, just like regular models.
@binaryfire
Copy link
Contributor Author

@albertcht I've also added HasCollection support to Pivot and MorphPivot to match Laravel's behavior.

Since Hypervel's pivot classes extend Hyperf's base classes (not Hypervel's Model), they don't inherit collection functionality automatically. I've added the HasCollection trait and $collectionClass property to both classes.

This required changing Collection's template constraint from TModel of \Hypervel\...\Model to TModel of \Hyperf\...\Model so it works with any Hyperf-based model. This is correct since Hypervel's Collection extends Hyperf's Collection anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants