Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Extended Relationships Laravel Package

mr-punyapal/laravel-extended-relationships

Adds efficient, extended Eloquent relationship helpers for Laravel models to cut queries, boost performance, and reduce duplicate code. Includes a HasExtendedRelationships trait and custom relations like belongsToManyKeys. Compatible with Laravel 11–13 and PHP 8.2+.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require mrpunyapal/laravel-extended-relationships
    
  2. Add the trait to your model:

    use MrPunyapal\LaravelExtendedRelationships\HasExtendedRelationships;
    
    class Post extends Model {
        use HasExtendedRelationships;
    }
    

First Use Case: Audit Trails

Define a belongsToManyKeys relationship to map multiple foreign keys (e.g., created_by, updated_by) to a single relationship:

public function auditors() {
    return $this->belongsToManyKeys(
        related: User::class,
        foreignKey: 'id',
        relations: [
            'created_by' => 'creator',
            'updated_by' => 'updater',
        ]
    );
}

Usage:

$post = Post::with('auditors')->find(1);
$post->auditors->creator; // User who created the post
$post->auditors->updater; // User who last updated it

Implementation Patterns

1. Audit Trail Pattern

Workflow:

  • Use belongsToManyKeys for models with audit columns (created_by, updated_by, deleted_by).
  • Access related users via dot notation ($model->relationship->creator).

Example:

// Post model
public function auditors() {
    return $this->belongsToManyKeys(
        User::class,
        'id',
        ['created_by' => 'creator', 'updated_by' => 'updater']
    );
}

// Controller
$posts = Post::with('auditors')->get();
foreach ($posts as $post) {
    $post->auditors->creator->name; // Eager-loaded
}

2. Array Column Relationships

Workflow:

  • Use hasManyArrayColumn for JSON/array columns storing IDs (e.g., tags, companies).
  • Define the inverse with belongsToArrayColumn if needed.

Example:

// User model (stores company IDs in JSON)
public function companies() {
    return $this->hasManyArrayColumn(
        Company::class,
        'id',
        'company_ids'
    );
}

// Company model (inverse)
public function users() {
    return $this->belongsToArrayColumn(
        User::class,
        'id',
        'company_ids',
        isString: true // If IDs are stored as strings
    );
}

3. Inverse Relationships

Workflow:

  • Use hasManyKeys for the inverse of belongsToManyKeys (e.g., UserPost).
  • Map local keys to relationship names (e.g., created_bycreated).

Example:

// User model
public function auditedPosts() {
    return $this->hasManyKeys(
        Post::class,
        ['created_by' => 'created', 'updated_by' => 'updated']
    );
}

4. Eager Loading Strategies

  • Always eager-load extended relationships to avoid N+1 queries:
    $posts = Post::with('auditors', 'auditors.creator', 'auditors.updater')->get();
    
  • Lazy-load only when necessary (e.g., in API responses):
    $post = Post::find(1);
    $post->load('auditors'); // Load on demand
    

5. Integration with Policies/Authorization

  • Use the relationships in policies for fine-grained access control:
    public function update(User $user, Post $post) {
        return $user->id === $post->auditors->updater->id;
    }
    

Gotchas and Tips

Pitfalls

  1. Query Overhead with isString: true:

    • Enabling isString: true in belongsToArrayColumn forces string comparisons, which can slow down queries if the column contains mixed types (e.g., ["7", 71]).
    • Fix: Ensure consistency in your database (store all IDs as strings or integers).
  2. Lazy-Loading Sorting Issues:

    • Relationships like belongsToManyKeys may return unsorted results when lazy-loaded.
    • Fix: Use sorted() to enforce order:
      $post->auditors->sorted('created_at', 'asc');
      
  3. Circular Dependencies:

    • Deeply nested relationships (e.g., Post → User → Post) can cause infinite loops.
    • Fix: Use with() selectively or break cycles with loadMissing().
  4. JSON Column Serialization:

    • If hasManyArrayColumn fails, verify the column is cast as array in $casts:
      protected $casts = ['company_ids' => 'array'];
      

Debugging Tips

  • Check Queries: Use Laravel Debugbar or Telescope to verify the package generates single queries for multiple relationships.

    \DB::enableQueryLog();
    $post = Post::with('auditors')->find(1);
    \DB::getQueryLog(); // Should show 1 query, not 3
    
  • Validate Relationship Data: Ensure foreign keys in relations arrays match the actual column names:

    // ❌ Wrong
    ['created_by' => 'creator', 'non_existent' => 'updater']
    
    // ✅ Correct
    ['created_by' => 'creator', 'updated_by' => 'updater']
    

Performance Optimization

  1. Batch Processing: Use cursor() for large datasets with hasManyArrayColumn:

    User::cursor()->with('companies')->each(function ($user) {
        // Process without loading all into memory
    });
    
  2. Caching: Cache frequently accessed relationships (e.g., audit trails):

    public function auditors() {
        return $this->belongsToManyKeys(
            User::class,
            'id',
            ['created_by' => 'creator']
        )->remember(60); // Cache for 60 minutes
    }
    

Extension Points

  1. Custom Query Modifiers: Override the relationship query in your model:

    public function auditors() {
        return $this->belongsToManyKeys(
            User::class,
            'id',
            ['created_by' => 'creator']
        )->where('active', 1); // Add custom conditions
    }
    
  2. Dynamic Relationships: Use dynamic properties for runtime-defined relationships:

    public function getDynamicAuditorsAttribute() {
        return $this->belongsToManyKeys(
            User::class,
            'id',
            ['created_by' => 'creator']
        )->getResults();
    }
    
  3. Boost Integration: Leverage Laravel Boost for IDE hints and documentation:

    php artisan boost:update --discover
    
    • Boost will suggest relationship methods and usage patterns.

Configuration Quirks

  • Boost Skill: Ensure Boost is installed (composer require laravel/boost --dev) to unlock IDE support for the package’s methods.

    • Run php artisan boost:install if Boost is missing.
  • PHP 8.2+ Features: The package uses generics (PHP 8.2+) for type safety. If you encounter issues, ensure your project’s php.ini enables:

    opcache.enable=1
    opcache.jit_buffer_size=100M
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle