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

Compoships Laravel Package

awobaz/compoships

Compoships enables Laravel Eloquent relationships on composite keys—match two or more columns for hasOne/hasMany/belongsTo, including eager loading. Ideal for legacy or third‑party schemas where single-column foreign keys aren’t possible.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require awobaz/compoships
    

    Add the package to your composer.json and run composer update.

  2. Model Integration: Choose one of these approaches for your models:

    • Extend the base model:
      use Awobaz\Compoships\Database\Eloquent\Model;
      class User extends Model { ... }
      
    • Use the trait:
      use Awobaz\Compoships\Compoships;
      class User { use Compoships; ... }
      
  3. Define a Composite Relationship:

    public function tasks()
    {
        return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id']);
    }
    
  4. Test with Eager Loading:

    $users = User::with('tasks')->get();
    

    Verify the relationship works as expected.

First Use Case

Scenario: A User has many Tasks, but tasks are scoped to both a team_id and category_id (e.g., a user manages tasks for a specific team/category combo). Solution:

// User model
public function tasks()
{
    return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id']);
}

// Task model (inverse relationship)
public function user()
{
    return $this->belongsTo(User::class, ['team_id', 'category_id'], ['team_id', 'category_id']);
}

Key Check: Ensure the inverse relationship is defined in the related model (Task in this case).


Implementation Patterns

Core Workflows

1. Basic Composite Relationships

  • One-to-Many:
    // Parent model (e.g., Team)
    public function users()
    {
        return $this->hasMany(User::class, ['team_id', 'department_id'], ['team_id', 'department_id']);
    }
    
  • Many-to-One:
    // Child model (e.g., User)
    public function team()
    {
        return $this
            ->belongsTo(Team::class, ['team_id', 'department_id'], ['id', 'department_id']);
    }
    
  • Note: The order of keys in the array matters for alignment (e.g., ['foreign1', 'foreign2'] must match ['local1', 'local2']).

2. Eager Loading with Constraints

Use where clauses after defining the relationship to avoid eager-loading issues:

$teams = Team::with(['users' => function ($query) {
    $query->where('is_active', 1);
}])->get();

3. Factories for Testing

Extend your factory with the ComposhipsFactory trait to support composite relationships:

use Awobaz\Compoships\Database\Eloquent\Factories\ComposhipsFactory;
class UserFactory extends Factory
{
    use ComposhipsFactory;
    // ...
}

Then use has() with composite keys:

User::factory()->has(
    Task::factory()->count(3),
    'tasks',
    ['team_id' => 1, 'category_id' => 2]
);

4. Dynamic Composite Keys

For relationships where keys are dynamic (e.g., based on user input), use closures:

public function dynamicTasks($teamId, $categoryId)
{
    return $this->hasMany(Task::class)
        ->where('team_id', $teamId)
        ->where('category_id', $categoryId);
}

Warning: This bypasses Compoships' eager-loading optimizations. Use only when necessary.

5. Polymorphic-Like Patterns

Simulate polymorphic relationships with composite keys:

// Model: Comment
public function commentable()
{
    return $this->morphTo(['id', 'commentable_type'], ['commentable_id', 'commentable_type']);
}

Limitation: Only hasOne, hasMany, and belongsTo are supported. For morphTo, use a custom solution or extend the package.


Integration Tips

1. Database Indexing

Ensure composite keys are indexed in the database:

ALTER TABLE tasks ADD INDEX idx_team_category (team_id, category_id);

Performance Impact: Without indexes, queries will be slow even with Compoships.

2. API Resource Relationships

Use Laravel's API resources to shape composite relationships:

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

3. Query Scopes

Create reusable scopes for composite relationships:

public function scopeActive($query)
{
    return $query->where(function ($q) {
        $q->where('status', 'active')
          ->orWhere(function ($q) {
              $q->where('team_id', auth()->user()->team_id)
                ->where('category_id', auth()->user()->category_id);
          });
    });
}

4. Caching Strategies

Cache composite relationships to reduce database load:

$cacheKey = "user_{$user->id}_tasks_{$user->team_id}_{$user->category_id}";
return Cache::remember($cacheKey, now()->addHours(1), function () use ($user) {
    return $user->tasks()->get();
});

5. Migration Path Planning

Document the exit strategy for Compoships:

  • Short-term: Use Compoships for legacy systems.
  • Long-term: Replace with surrogate keys (e.g., user_id + task_iduser_task_id).
  • Tooling: Use Laravel migrations to add surrogate keys incrementally:
    Schema::table('tasks', function (Blueprint $table) {
        $table->unsignedBigInteger('user_task_id')->unique();
    });
    

Gotchas and Tips

Pitfalls

1. Eager-Loading Failures

  • Issue: Relationships with where clauses fail during eager loading because $this->localKey is null.
  • Fix: Use Compoships for the base relationship and add constraints after eager loading:
    // WRONG (fails with eager loading)
    return $this->hasMany(Task::class)->where('team_id', $this->team_id);
    
    // CORRECT (use Compoships for the base relationship)
    return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id']);
    

2. Nullable Columns

  • Issue: Relationships with all null composite keys fail.
  • Fix: Ensure at least one key is non-null or handle null cases explicitly:
    public function tasks()
    {
        return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id'])
            ->where(function ($query) {
                $query->whereNotNull('team_id')
                      ->orWhereNotNull('category_id');
            });
    }
    

3. Table Prefix Issues

  • Issue: Queries may fail if the table prefix (e.g., prefix_) isn’t applied correctly.
  • Fix: Update to Compoships 2.5.3+ (includes fix for table prefixes).
  • Workaround: Manually qualify table names:
    return $this->hasMany(Task::class, ['prefix_team_id', 'prefix_category_id'], ['team_id', 'category_id']);
    

4. Inverse Relationship Mismatches

  • Issue: Defining hasMany in Model A but forgetting the belongsTo in Model B.
  • Fix: Always define inverse relationships for consistency:
    // Model A (hasMany)
    public function bs()
    {
        return $this->hasMany(B::class, ['fk1', 'fk2'], ['local1', 'local2']);
    }
    
    // Model B (belongsTo)
    public function a()
    {
        return $this->belongsTo(A::class, ['fk1', 'fk2'], ['local1', 'local2']);
    }
    

5. Laravel Version Conflicts

  • Issue: Compoships may not support newer Laravel versions (e.g., 14+).
  • Fix: Pin to a stable version (e.g., 2.5.5) and monitor for updates:
    "awobaz/compoships": "2.5.5"
    

6. Composite Key Order Sensitivity

  • Issue: Swapping the order of keys in the relationship definition breaks queries.
  • **Fix
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4