From 771060ab05eb5b30131d2713060db89ad7fca1e8 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:41:44 +0000 Subject: [PATCH 1/3] Add CollectedBy attribute 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) --- .../Eloquent/Attributes/CollectedBy.php | 33 +++ .../Eloquent/Concerns/HasCollection.php | 57 +++++ src/core/src/Database/Eloquent/Model.php | 13 +- .../Eloquent/Concerns/HasCollectionTest.php | 212 ++++++++++++++++++ 4 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 src/core/src/Database/Eloquent/Attributes/CollectedBy.php create mode 100644 src/core/src/Database/Eloquent/Concerns/HasCollection.php create mode 100644 tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php diff --git a/src/core/src/Database/Eloquent/Attributes/CollectedBy.php b/src/core/src/Database/Eloquent/Attributes/CollectedBy.php new file mode 100644 index 000000000..a7dcbbf06 --- /dev/null +++ b/src/core/src/Database/Eloquent/Attributes/CollectedBy.php @@ -0,0 +1,33 @@ +> $collectionClass + */ + public function __construct( + public string $collectionClass, + ) { + } +} diff --git a/src/core/src/Database/Eloquent/Concerns/HasCollection.php b/src/core/src/Database/Eloquent/Concerns/HasCollection.php new file mode 100644 index 000000000..e09582d5b --- /dev/null +++ b/src/core/src/Database/Eloquent/Concerns/HasCollection.php @@ -0,0 +1,57 @@ +, class-string>> + */ + protected static array $resolvedCollectionClasses = []; + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return Collection + */ + public function newCollection(array $models = []): Collection + { + static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? Collection::class); + + return new static::$resolvedCollectionClasses[static::class]($models); + } + + /** + * Resolve the collection class name from the CollectedBy attribute. + * + * @return null|class-string> + */ + 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; + } +} diff --git a/src/core/src/Database/Eloquent/Model.php b/src/core/src/Database/Eloquent/Model.php index 2fb1e5a64..a872d578f 100644 --- a/src/core/src/Database/Eloquent/Model.php +++ b/src/core/src/Database/Eloquent/Model.php @@ -10,6 +10,7 @@ use Hypervel\Context\Context; use Hypervel\Database\Eloquent\Concerns\HasAttributes; use Hypervel\Database\Eloquent\Concerns\HasCallbacks; +use Hypervel\Database\Eloquent\Concerns\HasCollection; use Hypervel\Database\Eloquent\Concerns\HasObservers; use Hypervel\Database\Eloquent\Concerns\HasRelations; use Hypervel\Database\Eloquent\Concerns\HasRelationships; @@ -68,9 +69,10 @@ abstract class Model extends BaseModel implements UrlRoutable, HasBroadcastChann { use HasAttributes; use HasCallbacks; + use HasCollection; + use HasObservers; use HasRelations; use HasRelationships; - use HasObservers; protected ?string $connection = null; @@ -89,15 +91,6 @@ public function newModelBuilder($query) return new Builder($query); } - /** - * @param array $models - * @return \Hypervel\Database\Eloquent\Collection - */ - public function newCollection(array $models = []) - { - return new Collection($models); - } - public function broadcastChannelRoute(): string { return str_replace('\\', '.', get_class($this)) . '.{' . Str::camel(class_basename($this)) . '}'; diff --git a/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php b/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php new file mode 100644 index 000000000..bc2c3dffb --- /dev/null +++ b/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php @@ -0,0 +1,212 @@ +newCollection([]); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertNotInstanceOf(CustomTestCollection::class, $collection); + } + + public function testNewCollectionReturnsCustomCollectionWhenAttributePresent(): void + { + $model = new HasCollectionTestModelWithAttribute(); + + $collection = $model->newCollection([]); + + $this->assertInstanceOf(CustomTestCollection::class, $collection); + } + + public function testNewCollectionPassesModelsToCollection(): void + { + $model1 = new HasCollectionTestModel(); + $model2 = new HasCollectionTestModel(); + + $collection = $model1->newCollection([$model1, $model2]); + + $this->assertCount(2, $collection); + $this->assertSame($model1, $collection[0]); + $this->assertSame($model2, $collection[1]); + } + + public function testNewCollectionCachesResolvedCollectionClass(): void + { + $model1 = new HasCollectionTestModelWithAttribute(); + $model2 = new HasCollectionTestModelWithAttribute(); + + // First call should resolve and cache + $collection1 = $model1->newCollection([]); + + // Second call should use cache + $collection2 = $model2->newCollection([]); + + // Both should be CustomTestCollection + $this->assertInstanceOf(CustomTestCollection::class, $collection1); + $this->assertInstanceOf(CustomTestCollection::class, $collection2); + } + + public function testResolveCollectionFromAttributeReturnsNullWhenNoAttribute(): void + { + $model = new HasCollectionTestModel(); + + $result = $model->testResolveCollectionFromAttribute(); + + $this->assertNull($result); + } + + public function testResolveCollectionFromAttributeReturnsCollectionClassWhenAttributePresent(): void + { + $model = new HasCollectionTestModelWithAttribute(); + + $result = $model->testResolveCollectionFromAttribute(); + + $this->assertSame(CustomTestCollection::class, $result); + } + + public function testDifferentModelsUseDifferentCaches(): void + { + $modelWithoutAttribute = new HasCollectionTestModel(); + $modelWithAttribute = new HasCollectionTestModelWithAttribute(); + + $collection1 = $modelWithoutAttribute->newCollection([]); + $collection2 = $modelWithAttribute->newCollection([]); + + $this->assertInstanceOf(Collection::class, $collection1); + $this->assertNotInstanceOf(CustomTestCollection::class, $collection1); + $this->assertInstanceOf(CustomTestCollection::class, $collection2); + } + + public function testChildModelWithoutAttributeUsesDefaultCollection(): void + { + $model = new HasCollectionTestChildModel(); + + $collection = $model->newCollection([]); + + // PHP attributes are not inherited - child needs its own attribute + $this->assertInstanceOf(Collection::class, $collection); + $this->assertNotInstanceOf(CustomTestCollection::class, $collection); + } + + public function testChildModelWithOwnAttributeUsesOwnCollection(): void + { + $model = new HasCollectionTestChildModelWithOwnAttribute(); + + $collection = $model->newCollection([]); + + $this->assertInstanceOf(AnotherCustomTestCollection::class, $collection); + } +} + +// Test fixtures + +class HasCollectionTestModel extends Model +{ + protected ?string $table = 'test_models'; + + /** + * Expose protected method for testing. + */ + public function testResolveCollectionFromAttribute(): ?string + { + return $this->resolveCollectionFromAttribute(); + } + + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomTestCollection::class)] +class HasCollectionTestModelWithAttribute extends Model +{ + protected ?string $table = 'test_models'; + + /** + * Expose protected method for testing. + */ + public function testResolveCollectionFromAttribute(): ?string + { + return $this->resolveCollectionFromAttribute(); + } + + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +class HasCollectionTestChildModel extends HasCollectionTestModelWithAttribute +{ + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(AnotherCustomTestCollection::class)] +class HasCollectionTestChildModelWithOwnAttribute extends HasCollectionTestModelWithAttribute +{ + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +/** + * @template TKey of array-key + * @template TModel of Model + * @extends Collection + */ +class CustomTestCollection extends Collection +{ +} + +/** + * @template TKey of array-key + * @template TModel of Model + * @extends Collection + */ +class AnotherCustomTestCollection extends Collection +{ +} From 9059ed77c123f721bd3fcc7374b144afdae2279a Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:50:33 +0000 Subject: [PATCH 2/3] Add $collectionClass property support for custom collections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Eloquent/Concerns/HasCollection.php | 11 ++-- src/core/src/Database/Eloquent/Model.php | 10 +++ .../Eloquent/Concerns/HasCollectionTest.php | 63 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/core/src/Database/Eloquent/Concerns/HasCollection.php b/src/core/src/Database/Eloquent/Concerns/HasCollection.php index e09582d5b..af16b4123 100644 --- a/src/core/src/Database/Eloquent/Concerns/HasCollection.php +++ b/src/core/src/Database/Eloquent/Concerns/HasCollection.php @@ -9,10 +9,13 @@ use ReflectionClass; /** - * Provides support for the CollectedBy attribute on models. + * Provides support for custom collection classes on models. * - * This trait allows models to declare their collection class using the - * #[CollectedBy] attribute instead of overriding the newCollection method. + * 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 { @@ -31,7 +34,7 @@ trait HasCollection */ public function newCollection(array $models = []): Collection { - static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? Collection::class); + static::$resolvedCollectionClasses[static::class] ??= ($this->resolveCollectionFromAttribute() ?? static::$collectionClass); return new static::$resolvedCollectionClasses[static::class]($models); } diff --git a/src/core/src/Database/Eloquent/Model.php b/src/core/src/Database/Eloquent/Model.php index 637f84ebf..0f71ce99a 100644 --- a/src/core/src/Database/Eloquent/Model.php +++ b/src/core/src/Database/Eloquent/Model.php @@ -82,6 +82,16 @@ abstract class Model extends BaseModel implements UrlRoutable, HasBroadcastChann 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> + */ + protected static string $collectionClass = Collection::class; + protected ?string $connection = null; public function resolveRouteBinding($value) diff --git a/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php b/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php index bc2c3dffb..37fb8e6cc 100644 --- a/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php +++ b/tests/Core/Database/Eloquent/Concerns/HasCollectionTest.php @@ -22,6 +22,8 @@ protected function tearDown(): void HasCollectionTestModelWithAttribute::clearResolvedCollectionClasses(); HasCollectionTestChildModel::clearResolvedCollectionClasses(); HasCollectionTestChildModelWithOwnAttribute::clearResolvedCollectionClasses(); + HasCollectionTestModelWithProperty::clearResolvedCollectionClasses(); + HasCollectionTestModelWithAttributeAndProperty::clearResolvedCollectionClasses(); parent::tearDown(); } @@ -123,6 +125,26 @@ public function testChildModelWithOwnAttributeUsesOwnCollection(): void $this->assertInstanceOf(AnotherCustomTestCollection::class, $collection); } + + public function testNewCollectionUsesCollectionClassPropertyWhenNoAttribute(): void + { + $model = new HasCollectionTestModelWithProperty(); + + $collection = $model->newCollection([]); + + $this->assertInstanceOf(PropertyTestCollection::class, $collection); + } + + public function testAttributeTakesPrecedenceOverCollectionClassProperty(): void + { + $model = new HasCollectionTestModelWithAttributeAndProperty(); + + $collection = $model->newCollection([]); + + // Attribute should win over property + $this->assertInstanceOf(CustomTestCollection::class, $collection); + $this->assertNotInstanceOf(PropertyTestCollection::class, $collection); + } } // Test fixtures @@ -210,3 +232,44 @@ class CustomTestCollection extends Collection class AnotherCustomTestCollection extends Collection { } + +class HasCollectionTestModelWithProperty extends Model +{ + protected ?string $table = 'test_models'; + + protected static string $collectionClass = PropertyTestCollection::class; + + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomTestCollection::class)] +class HasCollectionTestModelWithAttributeAndProperty extends Model +{ + protected ?string $table = 'test_models'; + + // Property should be ignored when attribute is present + protected static string $collectionClass = PropertyTestCollection::class; + + /** + * Clear the static cache for testing. + */ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +/** + * @template TKey of array-key + * @template TModel of Model + * @extends Collection + */ +class PropertyTestCollection extends Collection +{ +} From 61b4a91a9ab1445593c57ce969e224f494862b5d Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:10:32 +0000 Subject: [PATCH 3/3] Add HasCollection support to Pivot and MorphPivot 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. --- src/core/src/Database/Eloquent/Collection.php | 2 +- .../Eloquent/Concerns/HasCollection.php | 6 +- src/core/src/Database/Eloquent/Model.php | 2 +- .../Eloquent/Relations/MorphPivot.php | 13 + .../src/Database/Eloquent/Relations/Pivot.php | 13 + .../Relations/PivotCollectionTest.php | 256 ++++++++++++++++++ 6 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 tests/Core/Database/Eloquent/Relations/PivotCollectionTest.php diff --git a/src/core/src/Database/Eloquent/Collection.php b/src/core/src/Database/Eloquent/Collection.php index 5533fd68e..a3dbeead8 100644 --- a/src/core/src/Database/Eloquent/Collection.php +++ b/src/core/src/Database/Eloquent/Collection.php @@ -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 * diff --git a/src/core/src/Database/Eloquent/Concerns/HasCollection.php b/src/core/src/Database/Eloquent/Concerns/HasCollection.php index af16b4123..bb3aff66a 100644 --- a/src/core/src/Database/Eloquent/Concerns/HasCollection.php +++ b/src/core/src/Database/Eloquent/Concerns/HasCollection.php @@ -22,7 +22,7 @@ trait HasCollection /** * The resolved collection class names by model. * - * @var array, class-string>> + * @var array> */ protected static array $resolvedCollectionClasses = []; @@ -30,7 +30,7 @@ trait HasCollection * Create a new Eloquent Collection instance. * * @param array $models - * @return Collection + * @return \Hypervel\Database\Eloquent\Collection */ public function newCollection(array $models = []): Collection { @@ -42,7 +42,7 @@ public function newCollection(array $models = []): Collection /** * Resolve the collection class name from the CollectedBy attribute. * - * @return null|class-string> + * @return null|class-string */ protected function resolveCollectionFromAttribute(): ?string { diff --git a/src/core/src/Database/Eloquent/Model.php b/src/core/src/Database/Eloquent/Model.php index 0f71ce99a..7bceee94b 100644 --- a/src/core/src/Database/Eloquent/Model.php +++ b/src/core/src/Database/Eloquent/Model.php @@ -88,7 +88,7 @@ abstract class Model extends BaseModel implements UrlRoutable, HasBroadcastChann * Override this property to use a custom collection class. Alternatively, * use the #[CollectedBy] attribute for a more declarative approach. * - * @var class-string> + * @var class-string */ protected static string $collectionClass = Collection::class; diff --git a/src/core/src/Database/Eloquent/Relations/MorphPivot.php b/src/core/src/Database/Eloquent/Relations/MorphPivot.php index ac4a89b11..f0366e8b4 100644 --- a/src/core/src/Database/Eloquent/Relations/MorphPivot.php +++ b/src/core/src/Database/Eloquent/Relations/MorphPivot.php @@ -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; @@ -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 + */ + protected static string $collectionClass = Collection::class; + /** * Delete the pivot model record from the database. * diff --git a/src/core/src/Database/Eloquent/Relations/Pivot.php b/src/core/src/Database/Eloquent/Relations/Pivot.php index 47d9468a7..0741637c0 100644 --- a/src/core/src/Database/Eloquent/Relations/Pivot.php +++ b/src/core/src/Database/Eloquent/Relations/Pivot.php @@ -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; @@ -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 + */ + protected static string $collectionClass = Collection::class; + /** * Delete the pivot model record from the database. * diff --git a/tests/Core/Database/Eloquent/Relations/PivotCollectionTest.php b/tests/Core/Database/Eloquent/Relations/PivotCollectionTest.php new file mode 100644 index 000000000..7a9fb0f8c --- /dev/null +++ b/tests/Core/Database/Eloquent/Relations/PivotCollectionTest.php @@ -0,0 +1,256 @@ +newCollection([]); + + $this->assertInstanceOf(Collection::class, $collection); + } + + public function testPivotNewCollectionReturnsCustomCollectionWhenAttributePresent(): void + { + $pivot = new PivotCollectionTestPivotWithAttribute(); + + $collection = $pivot->newCollection([]); + + $this->assertInstanceOf(CustomPivotCollection::class, $collection); + } + + public function testPivotNewCollectionUsesCollectionClassPropertyWhenNoAttribute(): void + { + $pivot = new PivotCollectionTestPivotWithProperty(); + + $collection = $pivot->newCollection([]); + + $this->assertInstanceOf(PropertyPivotCollection::class, $collection); + } + + public function testPivotAttributeTakesPrecedenceOverCollectionClassProperty(): void + { + $pivot = new PivotCollectionTestPivotWithAttributeAndProperty(); + + $collection = $pivot->newCollection([]); + + // Attribute should win over property + $this->assertInstanceOf(CustomPivotCollection::class, $collection); + $this->assertNotInstanceOf(PropertyPivotCollection::class, $collection); + } + + public function testPivotNewCollectionPassesModelsToCollection(): void + { + $pivot1 = new PivotCollectionTestPivot(); + $pivot2 = new PivotCollectionTestPivot(); + + $collection = $pivot1->newCollection([$pivot1, $pivot2]); + + $this->assertCount(2, $collection); + $this->assertSame($pivot1, $collection[0]); + $this->assertSame($pivot2, $collection[1]); + } + + // ========================================================================= + // MorphPivot Tests + // ========================================================================= + + public function testMorphPivotNewCollectionReturnsHypervelCollectionByDefault(): void + { + $pivot = new PivotCollectionTestMorphPivot(); + + $collection = $pivot->newCollection([]); + + $this->assertInstanceOf(Collection::class, $collection); + } + + public function testMorphPivotNewCollectionReturnsCustomCollectionWhenAttributePresent(): void + { + $pivot = new PivotCollectionTestMorphPivotWithAttribute(); + + $collection = $pivot->newCollection([]); + + $this->assertInstanceOf(CustomMorphPivotCollection::class, $collection); + } + + public function testMorphPivotNewCollectionUsesCollectionClassPropertyWhenNoAttribute(): void + { + $pivot = new PivotCollectionTestMorphPivotWithProperty(); + + $collection = $pivot->newCollection([]); + + $this->assertInstanceOf(PropertyMorphPivotCollection::class, $collection); + } + + public function testMorphPivotAttributeTakesPrecedenceOverCollectionClassProperty(): void + { + $pivot = new PivotCollectionTestMorphPivotWithAttributeAndProperty(); + + $collection = $pivot->newCollection([]); + + // Attribute should win over property + $this->assertInstanceOf(CustomMorphPivotCollection::class, $collection); + $this->assertNotInstanceOf(PropertyMorphPivotCollection::class, $collection); + } +} + +// ========================================================================= +// Pivot Test Fixtures +// ========================================================================= + +class PivotCollectionTestPivot extends Pivot +{ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomPivotCollection::class)] +class PivotCollectionTestPivotWithAttribute extends Pivot +{ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +class PivotCollectionTestPivotWithProperty extends Pivot +{ + protected static string $collectionClass = PropertyPivotCollection::class; + + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomPivotCollection::class)] +class PivotCollectionTestPivotWithAttributeAndProperty extends Pivot +{ + protected static string $collectionClass = PropertyPivotCollection::class; + + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +// ========================================================================= +// MorphPivot Test Fixtures +// ========================================================================= + +class PivotCollectionTestMorphPivot extends MorphPivot +{ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomMorphPivotCollection::class)] +class PivotCollectionTestMorphPivotWithAttribute extends MorphPivot +{ + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +class PivotCollectionTestMorphPivotWithProperty extends MorphPivot +{ + protected static string $collectionClass = PropertyMorphPivotCollection::class; + + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +#[CollectedBy(CustomMorphPivotCollection::class)] +class PivotCollectionTestMorphPivotWithAttributeAndProperty extends MorphPivot +{ + protected static string $collectionClass = PropertyMorphPivotCollection::class; + + public static function clearResolvedCollectionClasses(): void + { + static::$resolvedCollectionClasses = []; + } +} + +// ========================================================================= +// Custom Collection Classes +// ========================================================================= + +/** + * @template TKey of array-key + * @template TModel + * @extends Collection + */ +class CustomPivotCollection extends Collection +{ +} + +/** + * @template TKey of array-key + * @template TModel + * @extends Collection + */ +class PropertyPivotCollection extends Collection +{ +} + +/** + * @template TKey of array-key + * @template TModel + * @extends Collection + */ +class CustomMorphPivotCollection extends Collection +{ +} + +/** + * @template TKey of array-key + * @template TModel + * @extends Collection + */ +class PropertyMorphPivotCollection extends Collection +{ +}