pfrug/composite-key

Support for composite primary keys in Laravel Eloquent models.

v2.0.0 2025-09-09 08:25 UTC

This package is auto-updated.

Last update: 2025-09-09 13:54:42 UTC


README

pfrug/composite-key provides support for composite primary keys in Eloquent models.

Laravel does not natively support composite primary keys, as mentioned in the official documentation:
https://laravel.com/docs/12.x/eloquent#composite-primary-keys

This package offers a lightweight and flexible solution to enable composite key support for:

  • find() and findOrFail() methods
  • save() and delete() operations
  • Basic Eloquent relationships:
    • hasManyComposite()
    • belongsToComposite()

Installation

composer require pfrug/composite-key

Usage

Model Setup

use Pfrug\CompositeKey\Traits\HasCompositeKey;
use Illuminate\Database\Eloquent\Model;

class ShipmentHeader extends Model
{
    use HasCompositeKey;

    protected array $compositeKey = ['company_code', 'shipment_number'];
}

Finding Records

ShipmentHeader::find(['COMP001', 'SHIP123']);
ShipmentHeader::findOrFail(['COMP001', 'SHIP123']);

Relationships

hasManyComposite

$this->hasManyComposite(
    RelatedModel::class,
    ['foreign_key_1', 'foreign_key_2'],
    ['local_key_1', 'local_key_2']
);

belongsToComposite

$this->belongsToComposite(
    RelatedModel::class,
    ['foreign_key_1', 'foreign_key_2'],
    ['owner_key_1', 'owner_key_2']
);

Example: Composite Relationships in Practice

Student.php

class Student extends Model
{
    public function enrollments()
    {
        return $this->hasMany(Enrollment::class, 'student_id');
    }
}

Course.php

class Course extends Model
{
    public function enrollments()
    {
        return $this->hasMany(Enrollment::class, 'course_id');
    }
}

Enrollment.php

use Pfrug\CompositeKey\Models\CompositeModel;

class Enrollment extends CompositeModel
{
    protected $compositeKey = ['student_id', 'course_id'];

    public function student()
    {
        return $this->belongsTo(Student::class, 'student_id');
    }

    public function course()
    {
        return $this->belongsTo(Course::class, 'course_id');
    }

    public function grades()
    {
        return $this->hasManyComposite(
            Grade::class,
            ['student_id', 'course_id'],
            ['student_id', 'course_id']
        );
    }
}

Grade.php

use Pfrug\CompositeKey\Models\CompositeModel;

class Grade extends CompositeModel
{
    protected $compositeKey = ['student_id', 'course_id'];

    public function enrollment()
    {
        return $this->belongsToComposite(
            Enrollment::class,
            ['student_id', 'course_id'],
            ['student_id', 'course_id']
        );
    }
}

Sample Usage

// Retrieve record with composite key
$enrollment = Enrollment::find([1, 1]);

// Update record
$enrollment->grade = 'B+';
$enrollment->save();

// Access belongsTo relation
echo $enrollment->student->name;

// Access hasManyComposite relation
foreach ($enrollment->grades as $grade) {
    echo $grade->value;
}

// Access belongsToComposite relation from Grade
$grade = Grade::first();
echo $grade->enrollment->course_id;

Eager Loading with Composite Keys

Composite relationships defined with hasManyComposite and belongsToComposite fully support eager loading via with() or load().

Example

use App\Models\Course;
use App\Models\Grade;

// 1) Eager-load enrollments and grades when retrieving courses
$courses = Course::with(['enrollments.grades'])->get();

foreach ($courses as $course) {
    foreach ($course->enrollments as $enrollment) {
        foreach ($enrollment->grades as $grade) {
            echo $grade->value;
        }
    }
}

// 2) Eager-load course and student when retrieving grades
$grades = Grade::with(['enrollment.course', 'enrollment.student'])->get();

foreach ($grades as $grade) {
    echo $grade->enrollment->course->name;
    echo $grade->enrollment->student->name;
}

Compatibility

  • Laravel 10+
  • PHP 8.2+

License

MIT