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

Add a custom Eloquent hasManyMerged relationship to merge multiple hasMany relations into one collection. Query, eager load, sort, and paginate merged results as a single relation while keeping models and constraints intact.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require korridor/laravel-has-many-merged
    

    No additional configuration or service provider setup is required.

  2. Define the Relationship: In your Eloquent model (e.g., Post.php), replace manual merge logic with hasManyMerged:

    use Korridor\HasManyMerged\HasManyMerged;
    
    public function comments(): HasManyMerged
    {
        return $this->hasManyMerged([
            // Primary relationship (e.g., direct comments)
            $this->hasMany(Comment::class),
    
            // Secondary relationship (e.g., replies)
            $this->hasMany(Reply::class)->where('parent_id', $this->id),
        ]);
    }
    
  3. First Use Case: Fetch merged results in a controller or blade view:

    $post = Post::with('comments')->find(1);
    // $post->comments now contains a unified collection of Comment and Reply models.
    
  4. Key Files to Review:


Implementation Patterns

Usage Patterns

1. Basic Merging

Combine multiple hasMany relationships into a single query:

public function transactions(): HasManyMerged
{
    return $this->hasManyMerged([
        $this->hasMany(Order::class),
        $this->hasMany(Payment::class),
        $this->hasMany(Refund::class),
    ]);
}

Workflow:

  • Use with() for eager loading:
    $user = User::with('transactions')->find(1);
    
  • Iterate over the merged collection:
    foreach ($user->transactions as $transaction) {
        // $transaction is an instance of Order|Payment|Refund
    }
    

2. Polymorphic Merging

Merge relationships from polymorphic models (e.g., Comment on Post/Article):

public function comments(): HasManyMerged
{
    return $this->hasManyMerged([
        $this->morphMany(Comment::class, 'commentable'),
        $this->morphMany(Reply::class, 'commentable'),
    ]);
}

Tip: Ensure polymorphic keys (commentable_id, commentable_type) match across models.

3. Conditional Merging

Dynamically include/exclude relationships based on logic:

public function activity(): HasManyMerged
{
    $relations = [$this->hasMany(ActivityLog::class)];
    if ($this->isAdmin()) {
        $relations[] = $this->hasMany(AuditLog::class);
    }
    return $this->hasManyMerged($relations);
}

4. Query Scoping

Apply constraints to the merged query:

$user->transactions()
     ->where('status', 'completed')
     ->whereBetween('created_at', [$startDate, $endDate])
     ->orderBy('amount', 'desc');

5. Eager Loading with Constraints

Combine with() and whereHas for optimized queries:

$posts = Post::with(['comments' => function ($query) {
    $query->where('is_public', true);
}])->get();

6. Custom Merge Logic

Override how models are merged (e.g., deduplication, field prioritization):

public function comments(): HasManyMerged
{
    return $this->hasManyMerged([
        $this->hasMany(Comment::class),
        $this->hasMany(Reply::class),
    ])->mergeUsing(function ($source, $target) {
        // Custom logic: e.g., prioritize Replies over Comments
        return $target->merge($source)->sortBy('created_at');
    });
}

Integration Tips

API Resources

Use JsonResource to serialize merged relationships:

public function toArray($request)
{
    return [
        'id' => $this->id,
        'transactions' => TransactionResource::collection($this->whenLoaded('transactions')),
    ];
}

Form Requests

Validate merged relationships in requests:

public function rules()
{
    return [
        'post_id' => 'required|exists:posts,id',
        'comments.*' => 'required|array', // Validates merged Comment/Reply structure
    ];
}

Testing

Test merged relationships with assertHasManyMerged (if available) or custom assertions:

public function test_merged_relationships()
{
    $post = Post::factory()->create();
    $comment = Comment::factory()->create(['post_id' => $post->id]);
    $reply = Reply::factory()->create(['post_id' => $post->id]);

    $this->assertCount(2, $post->comments);
    $this->assertInstanceOf(Comment::class, $post->comments->first());
    $this->assertInstanceOf(Reply::class, $post->comments->last());
}

Performance

  • Avoid N+1: Always use with() when accessing merged relationships.
  • Pagination: Use paginate() on merged queries for large datasets:
    $user->transactions()->paginate(10);
    
  • Select Fields: Limit fields with select() to reduce memory usage:
    $user->transactions()->select(['id', 'amount', 'created_at']);
    

Gotchas and Tips

Pitfalls

1. Polymorphic Key Mismatches

  • Issue: If polymorphic keys (e.g., commentable_id, commentable_type) don’t match across models, the merge will fail silently or return empty results.
  • Fix: Explicitly define keys in the relationship:
    $this->morphMany(Comment::class, 'commentable')
         ->where('commentable_type', Post::class);
    

2. Duplicate Entries

  • Issue: Merging relationships with overlapping data (e.g., same id in comments and replies) may cause duplicates.
  • Fix: Use mergeUsing to deduplicate:
    ->mergeUsing(function ($source, $target) {
        return $target->merge($source)->unique('id');
    });
    

3. Query Builder Limitations

  • Issue: Some Eloquent query methods (e.g., orWhere, groupBy) may not work as expected on merged queries.
  • Fix: Apply constraints to individual relationships before merging:
    $this->hasManyMerged([
        $this->hasMany(Comment::class)->where('is_approved', true),
        $this->hasMany(Reply::class)->where('is_approved', true),
    ]);
    

4. Memory Usage

  • Issue: Merging large datasets (e.g., >50K rows) can exhaust memory.
  • Fix: Use pagination or chunking:
    $user->transactions()->paginate(20);
    // OR
    $user->transactions()->cursor();
    

5. Type Safety

  • Issue: Merged collections contain mixed model types, which may cause type errors in strict PHP.
  • Fix: Use @phpstan-ignore-line or cast to a base model:
    /** @var \Illuminate\Support\Collection|\Korridor\HasManyMerged\HasManyMergedCollection */
    $collection = $user->transactions;
    

Debugging

1. Inspect Generated SQL

Use Laravel’s query logging or Debugbar to verify the merged query:

\DB::enableQueryLog();
$user->transactions->toSql(); // Check the final query
\DB::getQueryLog();

2. Check Merge Logic

Override mergeUsing to debug merging behavior:

->mergeUsing(function ($source, $target) {
    \Log::debug('Merging source:', $source->toArray());
    \Log::debug('Target before merge:', $target->toArray());
    return $target->merge($source);
});

3. Handle Missing Relationships

Ensure all

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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony