techziondev / laravel-polymorphic-entities
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0
Requires (Dev)
- illuminate/console: ^11.0|^12.0
- illuminate/events: ^11.0|^12.0
- laravel/pint: ^1.14
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.7
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2025-09-12 16:02:34 UTC
README
A powerful Laravel package that provides flexible polymorphic entity associations with time-based controls, metadata support, and a fluent API for managing complex model relationships.
Features
- Polymorphic Associations: Associate any model with multiple types of entities
- Time-Based Control: Set start and expiration dates for associations
- Metadata Support: Store custom metadata with each association
- Fluent API: Intuitive builder pattern for managing associations
- Event System: Built-in events for association lifecycle management
- Active Status Tracking: Enable/disable associations without deletion
- Automatic Cleanup: Console command for removing expired associations
Requirements
- PHP 8.2 or higher
- Laravel 11.0 or 12.0
- Illuminate Database package
Installation
You can install the package via Composer:
composer require techziondev/laravel-polymorphic-entities
Publish and Run Migrations
Publish the migration file:
php artisan vendor:publish --provider="Techzion\LaravelPolymorphicEntities\LaravelPolymorphicEntitiesServiceProvider" --tag="migrations"
Run the migrations:
php artisan migrate
Publish Configuration (Optional)
You can optionally publish the configuration file:
php artisan vendor:publish --provider="Techzion\LaravelPolymorphicEntities\LaravelPolymorphicEntitiesServiceProvider" --tag="config"
Basic Usage
Setting Up Your Models
1. Models That Can Be Associated (HasEntities)
Add the HasEntities
trait to models that can be associated with entities:
<?php use Illuminate\Database\Eloquent\Model; use Techzion\LaravelPolymorphicEntities\Traits\HasEntities; use Techzion\LaravelPolymorphicEntities\Contracts\Associable; class User extends Model implements Associable { use HasEntities; // Your model code... }
2. Entity Models (IsEntity)
Add the IsEntity
trait to models that act as entities:
<?php use Illuminate\Database\Eloquent\Model; use Techzion\LaravelPolymorphicEntities\Traits\IsEntity; use Techzion\LaravelPolymorphicEntities\Contracts\Entity; use Techzion\LaravelPolymorphicEntities\Contracts\HasMetadataSchema; class Role extends Model implements Entity, HasMetadataSchema { use IsEntity; // Your model code... } class Department extends Model implements Entity, HasMetadataSchema { use IsEntity; // Your model code... }
Creating Associations
Simple Association
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; $user = User::find(1); $role = Role::find(1); // Associate user with a role EntityManager::associate($user)->to($role)->associate();
Association with Metadata
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; EntityManager::associate($user) ->to($role) ->withMetadata([ 'assigned_by' => auth()->id(), 'department_id' => 5, 'notes' => 'Temporary assignment' ]) ->associate();
Time-Based Associations
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; EntityManager::associate($user) ->to($role) ->startsAt('2024-01-01 00:00:00') ->expiresAt('2024-12-31 23:59:59') ->withMetadata(['contract_type' => 'temporary']) ->associate();
Switch Associations
Replace existing associations with a new one:
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; // This will deactivate other role associations and create a new one EntityManager::associate($user) ->to($newRole) ->andSwitch() ->associate();
Retrieving Associations
Get Associated Entities
// Get all currently active entities for a user $user = User::find(1); $activeEntities = $user->entities(); // Collection of associated entities // Get entities of a specific type $userRoles = $user->entitiesOfType(Role::class);
Get Associated Models
// From an entity perspective - get all models associated with this role $role = Role::find(1); $associatedUsers = EntityManager::getAssociatedModels($role);
Check if Association Exists
// Check if user has a specific role if ($user->isAssociatedToEntity($role)) { // User has this role } // Check if user has any role of a specific type if ($user->isAssociatedToEntityType(Role::class)) { // User has at least one role }
Removing Associations
Dissociate from Specific Entity
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; EntityManager::dissociate($user)->from($role)->execute();
Force Dissociate (Hard Delete)
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; EntityManager::dissociate($user)->from($role)->force();
Advanced Usage
Working with Association Metadata
Updating Metadata
$association = $user->associableTo() ->where('entity_type', Role::class) ->where('entity_id', $role->id) ->first(); $association->update([ 'metadata' => array_merge($association->metadata ?? [], [ 'updated_by' => auth()->id(), 'updated_at' => now() ]) ]);
Querying by Metadata
// Find users with specific metadata $users = User::whereHas('associableTo', function ($query) { $query->where('metadata->department_id', 5); })->get();
Time-Based Association Management
Finding Expired Associations
$expiredAssociations = $user->associableTo() ->expired() ->get();
Finding Active Associations
$activeAssociations = $user->associableTo() ->currentlyValid() ->get();
Event Handling
The package fires several events during the association lifecycle:
use Techzion\LaravelPolymorphicEntities\Events\Associating; use Techzion\LaravelPolymorphicEntities\Events\Associated; use Techzion\LaravelPolymorphicEntities\Events\Dissociating; use Techzion\LaravelPolymorphicEntities\Events\Dissociated; use Techzion\LaravelPolymorphicEntities\Events\Switching; use Techzion\LaravelPolymorphicEntities\Events\Switched; // Listen to events in your EventServiceProvider protected $listen = [ Associated::class => [ YourAssociatedListener::class, ], Dissociated::class => [ YourDissociatedListener::class, ], ];
Console Commands
Clear Expired Associations
Remove all expired associations from the database:
php artisan entities:clear-expired
API Reference
EntityManager Methods
associate(Model $associable): Association
Creates a new association builder for the given model.
dissociate(Model $associable): Dissociation
Creates a new dissociation builder for the given model.
switchTo(Model $associable): SwitchTo
Creates a new switch builder for the given model.
getAssociatedModels(Model $entity): Collection
Gets all models associated with the given entity.
Association Builder Methods
to(Model $entity): self
Specifies the entity to associate with.
withMetadata(array $metadata): self
Adds metadata to the association.
startsAt(DateTime $date): self
Sets the start date for the association.
expiresAt(DateTime $date): self
Sets the expiration date for the association.
andSwitch(): self
Indicates this should replace existing associations.
associate(): EntityPivot
Executes the association and returns the pivot model.
Dissociation Builder Methods
from(Model $entity): self
Specifies the entity to dissociate from.
execute(): bool
Executes the dissociation (soft delete by default).
force(): bool
Executes the dissociation with hard delete.
HasEntities Trait Methods
associableTo(): MorphMany
Relationship to get entity associations.
entities(): Collection
Get currently active associated entities.
entitiesOfType(string $entityType): Collection
Get entities of a specific type.
isAssociatedToEntity(Model $entity): bool
Check if associated with a specific entity.
isAssociatedToEntityType(string $entityType): bool
Check if has any entity of the given type.
associateToEntity(Model $entity, array $options = []): EntityPivot
Associate this model with an entity.
dissociateFromEntity(Model $entity, bool $softDelete = true): bool
Dissociate this model from an entity.
IsEntity Trait Methods
associatedBy(): MorphMany
Relationship to get models associated with this entity.
associatedModels(): Collection
Get all currently associated models.
Configuration
The configuration file config/polymorphic-entities.php
contains:
return [ 'entity_pivot' => \Techzion\LaravelPolymorphicEntities\Models\EntityPivot::class, ];
Custom Pivot Model
You can extend the default pivot model if needed:
<?php use Techzion\LaravelPolymorphicEntities\Models\EntityPivot; class CustomEntityPivot extends EntityPivot { // Add custom methods or properties protected $casts = [ 'metadata' => 'array', 'is_active' => 'boolean', 'starts_at' => 'datetime', 'expires_at' => 'datetime', ]; }
Then update your configuration:
// config/polymorphic-entities.php return [ 'entity_pivot' => \App\Models\CustomEntityPivot::class, ];
Use Cases
User Role Management
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; // Assign a temporary admin role EntityManager::associate($user) ->to($adminRole) ->startsAt(now()) ->expiresAt(now()->addMonths(3)) ->withMetadata(['assigned_by' => $manager->id]) ->associate(); // Check if user is currently an admin if ($user->isAssociatedToEntity($adminRole)) { // Grant admin access }
Product Category Assignment
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; // Associate product with multiple categories EntityManager::associate($product)->to($electronicsCategory)->associate(); EntityManager::associate($product)->to($featuredCategory) ->expiresAt(now()->addWeeks(2)) ->associate(); // Featured for 2 weeks // Get all featured products $featuredProducts = Product::whereHas('associableTo', function ($query) use ($featuredCategory) { $query->where('entity_type', get_class($featuredCategory)) ->where('entity_id', $featuredCategory->id) ->currentlyValid(); })->get();
Project Team Membership
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; // Add user to project with specific role metadata EntityManager::associate($user) ->to($project) ->withMetadata([ 'role' => 'developer', 'hourly_rate' => 75.00, 'access_level' => 'read-write' ]) ->startsAt($project->start_date) ->expiresAt($project->end_date) ->associate();
Cheat Sheet
use Techzion\LaravelPolymorphicEntities\EntityFacade as EntityMananger; EntityManager::associate($user)->to($company)->associate(); EntityManager::associate($user)->to($company)->startsAt(now())->expiresAt(now()->addMonths(2))->associate(); EntityManager::associate($user) ->to($company) ->withMetadata([ 'assigned_by' => auth()->id() ]) ->associate(); EntityManager::dissociate($user)->from($company)->execute(); EntityManager::dissociate($user)->from($company)->force(); EntityManager::switchTo($user)->to($company)->execute(); EntityManager::isAssociatedTo($user, $company); EntityManager::hasAssociated($company, $user); $association = EntityManager::findAssociation($user, $company);
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.