techziondev/laravel-polymorphic-entities

v0.0.1 2025-09-11 17:51 UTC

This package is not auto-updated.

Last update: 2025-09-12 16:02:34 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

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.