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 Composite Relations Laravel Package

reedware/laravel-composite-relations

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:
    composer require reedware/laravel-composite-relations
    
  2. Extend Base Model: Add the trait to your base Model class:
    use Reedware\LaravelCompositeRelations\HasCompositeRelations;
    
    abstract class Model extends Eloquent {
        use HasCompositeRelations;
    }
    
  3. Define First Composite Relation: Replace a standard belongsTo with compositeBelongsTo:
    public function userRole() {
        return $this->compositeBelongsTo(UserRole::class, ['user_id', 'role_id'], ['user_id', 'role_id']);
    }
    
  4. Test Basic Usage:
    $userRole = UserRole::find(['user_id' => 1, 'role_id' => 2]);
    $userRole->user()->first(); // Test eager loading
    

Where to Look First

  • Package Docs: GitHub README for syntax and examples.
  • Migration Guide: Check release/v5.0.0.md for Laravel 11/12-specific changes (e.g., generics support).
  • Usage Patterns: Focus on compositeBelongsTo, compositeHasOne, and compositeHasMany methods.

First Use Case

Legacy System Integration: Convert a raw SQL query like:

SELECT * FROM user_roles ur
JOIN users u ON ur.user_id = u.id AND ur.role_id = u.default_role_id
WHERE ur.user_id = 1;

into an Eloquent relation:

public function defaultUserRole() {
    return $this->compositeBelongsTo(User::class, ['user_id', 'role_id'], ['id', 'default_role_id']);
}

Implementation Patterns

Core Workflows

1. Defining Composite Relations

  • Standard Syntax:
    // BelongsTo
    $this->compositeBelongsTo(RelatedModel::class, ['local_key1', 'local_key2'], ['foreign_key1', 'foreign_key2']);
    
    // HasOne/HasMany
    $this->compositeHasOne(RelatedModel::class, ['foreign_key1', 'foreign_key2'], ['local_key1', 'local_key2']);
    
  • Convention Over Configuration: Define $primaryKeys on the related model to omit keys:
    class UserRole extends Model {
        protected $primaryKeys = ['user_id', 'role_id'];
    }
    // Usage:
    $this->compositeBelongsTo(UserRole::class);
    

2. Eager Loading

  • Works identically to native Eloquent:
    $models = Model::with('compositeRelation')->get();
    
  • Query Scoping:
    $models = Model::whereHas('compositeRelation', function($query) {
        $query->where('role_id', '>', 1);
    })->get();
    

3. Joining Through Relations

  • Requires reedware/laravel-relation-joins:
    $task->joinRelation('importSummary', function($join) {
        $join->where('task_import_summaries.name', 'like', '%test%');
    });
    

4. Composite Key Logic

  • Glue Operator: Use 'and' for strict matching (default: 'or'):
    $this->compositeBelongsTo(Related::class, ['key1', 'key2'], ['fk1', 'fk2'], null, 'and');
    
    • SQL Output:
      -- 'or' (default)
      WHERE (fk1 = ? OR fk2 = ?) OR (fk1 = ? OR fk2 = ?)
      
      -- 'and'
      WHERE (fk1 = ? AND fk2 = ?) OR (fk1 = ? AND fk2 = ?)
      

Integration Tips

With Existing Code

  • Backward Compatibility: Composite relations do not affect existing single-key relations. Use them only for new or migrated relations.
  • Migration Strategy:
    1. Add composite relations to new models.
    2. Gradually replace raw SQL queries with composite relations.
    3. Use feature flags to toggle between old/new logic during transition.

Testing

  • Unit Tests: Test composite relations in isolation:
    public function test_composite_belongs_to() {
        $role = UserRole::find(['user_id' => 1, 'role_id' => 2]);
        $this->assertInstanceOf(User::class, $role->user()->first());
    }
    
  • Query Debugging: Use toSql() and getBindings() to verify generated queries:
    $query = $model->compositeRelation()->toSql();
    $bindings = $model->compositeRelation()->getBindings();
    

Performance

  • Indexing: Ensure composite foreign keys are indexed in the database:
    ALTER TABLE related_table ADD INDEX composite_index (foreign_key1, foreign_key2);
    
  • Avoid N+1: Always eager load composite relations in bulk operations:
    Model::with(['compositeRelation1', 'compositeRelation2'])->get();
    

Gotchas and Tips

Pitfalls

1. Composite Primary Keys

  • Issue: The package assumes the related model uses single-column primary keys (e.g., id). If the related model also has composite keys, queries may fail.
  • Workaround: Override the getForeignKeys() method in the related model:
    class RelatedModel extends Model {
        public function getForeignKeys() {
            return ['fk1', 'fk2']; // Explicitly define foreign keys
        }
    }
    

2. Glue Operator Misuse

  • Issue: Using 'and' with mismatched key counts can produce incorrect queries. Example:
    // ❌ Wrong: 3 local keys vs. 2 foreign keys
    $this->compositeBelongsTo(Related::class, ['key1', 'key2', 'key3'], ['fk1', 'fk2'], null, 'and');
    
  • Fix: Ensure arrays are the same length. Use 'or' for flexibility.

3. Eager Loading Quirks

  • Issue: Eager loading composite relations with complex constraints may not work as expected:
    // ❌ May not apply constraints correctly
    Model::with(['compositeRelation' => function($query) {
        $query->where('role_id', '>', 1);
    }])->get();
    
  • Fix: Use whereHas for constraints:
    Model::whereHas('compositeRelation', function($query) {
        $query->where('role_id', '>', 1);
    })->with('compositeRelation')->get();
    

4. Polymorphic Relations

  • Issue: The package does not support polymorphic composite relations (e.g., morphTo with composite keys).
  • Workaround: Use raw queries or intermediate models.

5. Laravel 11+ Generics

  • Issue: Some older Laravel versions may require type-hint adjustments for generics.
  • Fix: Update to the latest package version (v5.x+) for Laravel 11/12 support.

Debugging Tips

Query Inspection

  • Log Generated SQL:
    DB::enableQueryLog();
    $model->compositeRelation()->get();
    dd(DB::getQueryLog());
    
  • Check Bindings:
    $query = $model->compositeRelation()->toSql();
    $bindings = $model->compositeRelation()->getBindings();
    dd($query, $bindings);
    

Common Errors

Error Cause Solution
Column not found Mismatched key names in compositeBelongsTo Verify table columns and key arrays.
Call to undefined method Missing HasCompositeRelations trait Ensure the trait is added to the base model.
SQLSTATE[23000] Duplicate composite keys Add UNIQUE constraints or handle duplicates in application logic.
Binding mismatch Incorrect glue operator usage Use 'or' for flexibility or 'and' for strict matching.

Extension Points

Customizing Relation Logic

  • Override Query Builder: Extend the package’s query builder by publishing and modifying its configuration:
    // config/composite-relations.php
    'glue' => 'and', // Default glue for all relations
    
  • Add New Relation Types: The package is open-source. To add belongsToMany support:
    1. Fork the
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.
besmartand-pro/php-quality-config
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity