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 Has Many Merged Laravel Package

korridor/laravel-has-many-merged

Laravel package adding a “hasManyMerged” relationship to combine results from multiple hasMany relations into one merged collection/query. Useful for aggregating related models across different types or sources while keeping a familiar Eloquent API.

View on GitHub
Deep Wiki
Context7

Getting Started

First Steps

  1. Installation

    composer require korridor/laravel-has-many-merged
    

    No additional configuration or publisher required—just require the package.

  2. Basic Usage Define a merged relationship in your Eloquent model:

    use Korridor\HasManyMerged\HasManyMerged;
    
    class Post extends Model
    {
        public function mergedComments(): HasManyMerged
        {
            return $this->hasManyMerged(
                Comment::class, // Base model
                ['comments', 'replies'] // Relationship names to merge
            );
        }
    }
    
  3. First Query

    $post = Post::find(1);
    $mergedComments = $post->mergedComments; // Returns merged results from `comments` and `replies`
    
  4. Eager Loading

    $posts = Post::with('mergedComments')->get();
    

Where to Look First

  • GitHub README – Official documentation with examples.
  • Source Code – For advanced customization or debugging.
  • Tests – Real-world usage patterns and edge cases.

Implementation Patterns

1. Merging Standard hasMany Relationships

Use Case: Combine results from multiple hasMany relations (e.g., comments + replies).

class Post extends Model
{
    public function mergedComments(): HasManyMerged
    {
        return $this->hasManyMerged(
            Comment::class, // Base model
            ['comments', 'replies'] // Relationship names
        );
    }
}

Key Points:

  • Automatically queries all specified relations in a single pass.
  • Returns a merged collection (duplicates are preserved unless configured otherwise).

2. Polymorphic Relationship Merging

Use Case: Merge polymorphic relations (e.g., comments on Post and Article).

public function mergedComments(): HasManyMerged
{
    return $this->hasManyMerged(
        Comment::class,
        ['comments', 'replies'],
        ['commentable_id', 'commentable_type'] // Polymorphic keys
    );
}

Notes:

  • Works seamlessly with Laravel’s polymorphic support.
  • Ensure the foreign keys match the polymorphic structure of your models.

3. Customizing Merge Behavior

Use Case: Filter, order, or deduplicate merged results.

public function mergedComments(): HasManyMerged
{
    return $this->hasManyMerged(Comment::class, ['comments', 'replies'])
        ->orderBy('created_at', 'desc')
        ->where('is_active', true)
        ->unique('id'); // Deduplicate by ID
}

Advanced Customization:

  • Use mergeUsing() to apply custom logic during merging:
    $this->hasManyMerged(Comment::class, ['comments', 'replies'])
         ->mergeUsing(fn ($query) => $query->where('priority', 'high'));
    

4. Dynamic Relationship Merging

Use Case: Conditionally include/exclude relations based on runtime logic.

public function dynamicMergedComments(bool $includeReplies = true): HasManyMerged
{
    $relations = ['comments'];
    if ($includeReplies) {
        $relations[] = 'replies';
    }
    return $this->hasManyMerged(Comment::class, $relations);
}

Example Usage:

$post->dynamicMergedComments($user->hasPermission('view_replies'));

5. Integration with API Resources

Use Case: Serialize merged relations in JSON:API or custom responses.

class PostResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'comments' => $this->whenLoaded('mergedComments', fn () => $this->mergedComments),
        ];
    }
}

Notes:

  • Use whenLoaded() to avoid errors if the relationship isn’t eager-loaded.
  • Customize serialization with ->makeVisible()/->makeHidden() as needed.

6. Performance Optimization

Use Case: Reduce memory usage and query load.

$posts = Post::with(['mergedComments' => function ($query) {
    $query->limit(100); // Limit merged results
}])->get();

Best Practices:

  • Eager load merged relations to avoid N+1 queries.
  • Limit results early to reduce memory overhead.
  • Avoid complex mergeUsing in high-traffic endpoints.

7. Extending with Macros

Use Case: Add reusable query scopes or methods.

// In a service provider or model trait
HasManyMerged::macro('activeOnly', function () {
    return $this->where('is_active', true);
});

Usage:

$post->mergedComments->activeOnly();

Gotchas and Tips

1. Duplicate Handling

Pitfall: Merged results may contain duplicates if the same record exists in multiple relations. Solutions:

  • Deduplicate by ID:
    $merged = $post->mergedComments->unique('id');
    
  • Configure in the relationship:
    $this->hasManyMerged(Comment::class, ['comments', 'replies'])
         ->unique('id');
    
  • Custom deduplication logic:
    ->mergeUsing(fn ($query) => $query->whereNotIn('id', $alreadyMergedIds));
    

2. Polymorphic Key Mismatches

Pitfall: Incorrect polymorphic keys cause silent failures or incorrect joins. Debugging:

  • Verify commentable_id and commentable_type match your model’s structure.
  • Inspect the generated SQL:
    dd($this->mergedComments->getQuery()->toSql());
    
  • Test with a single relation first to isolate the issue.

3. Performance Pitfalls

Pitfall: Merging large datasets can be slow or memory-intensive. Mitigations:

  • Limit results early:
    $this->hasManyMerged(Comment::class, ['comments', 'replies'])
         ->limit(50);
    
  • Avoid eager loading all relations if only a subset is needed.
  • Use cursor() for large datasets:
    $post->mergedComments->cursor();
    

4. mergeUsing Behavior

Pitfall: mergeUsing callbacks run after all relations are loaded. Example:

$this->hasManyMerged(Comment::class, ['comments', 'replies'])
     ->mergeUsing(fn ($query) => $query->where('is_active', true));
// Filters the final merged result, not individual queries.

Workaround: Apply filters to individual relations first:

$this->hasManyMerged(Comment::class, ['comments', 'replies'])
     ->where(function ($query) {
         $query->where('is_active', true)
               ->orWhere('created_at', '>', now()->subDays(7));
     });

5. Serialization Issues

Pitfall: Merged relations may not serialize correctly in JSON. Fix:

  • Ensure the base model (Comment) implements JsonSerializable or uses Arrayable.
  • Hide pivot data if unwanted:
    $this->hasManyMerged(Comment::class, ['comments', 'replies'])
         ->makeHidden(['pivot']);
    

6. Testing Edge Cases

Common Test Scenarios:

  1. Empty relations: Verify behavior when one or both relations return no results.
  2. Duplicate IDs: Test deduplication logic.
  3. Polymorphic conflicts: Ensure correct model binding.
  4. Ordering: Confirm orderBy works as expected.

Example Test:

public function test_merged_relations_with_duplicates()
{
    $post = Post::factory()->create();
    $comment = Comment::factory()->create(['commentable_id' => $post->id]);

    // Insert duplicate in another relation
    Comment::factory()->create([
        'commentable_id' => $post->id,
        'id' => $comment->id, // Duplicate ID
    ]);

    $merged = $post->mergedComments->unique('id');
    $this->assertCount(1, $merged);
}

7. Debugging Queries

Tool: Use Laravel Debugbar or toSql() to inspect queries.

dd($this->mergedComments->getQuery()->toSql());

Common Issues:

  • Missing where clauses in polymorphic relations
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.
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
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope