Skip to content

Commit

Permalink
v0.1.0 - First release
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcastro committed Apr 23, 2019
0 parents commit 67419c5
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 0 deletions.
28 changes: 28 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "ajcastro/eager-load-pivot-relations",
"description": "Eager load pivot relations for Laravel Eloquent's BelongsToMany relation.",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Arjon Jason Castro",
"email": "ajcastro29@gmail.com"
}
],
"require": {
"php": ">=5.4.0"
},
"autoload": {
"psr-4": {
"AjCastro\\EagerLoadPivotRelations\\": "src"
}
},
"extra": {
"laravel": {
"providers": [
],
"aliases": {
}
}
}
}
139 changes: 139 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Laravel Eloquent: Eager Load Pivot Relations

Eager load pivot relations for Laravel Eloquent's BelongsToMany relation.

## Installation

```
composer require ajcastro/eager-load-pivot-relations
```

## Usage and Example

There are use-cases where in a pivot model has relations to be eager loaded.
Example, in a procurement system, we have the following:

**Tables**

```
items
- id
- name
units
- id
- name (pc, box, etc...)
plans (annual procurement plan)
- id
plan_item (pivot for plans and items)
- id
- plan_id
- item_id
- unit_id
```

**Models**

```php

class Unit extends \Eloquent {
protected $fillable = [];
}

use AjCastro\EagerLoadPivotRelations\EagerLoadPivotTrait;
class Item extends \Eloquent
{
// Use the trait here to override eloquent builder.
// It is used in this model because it is the relation model defined in
// Plan::items() relation.
use EagerLoadPivotTrait;

protected $fillable = [];

public function plans()
{
return $this->belongsToMany('Plan', 'plan_item');
}
}

class Plan extends \Eloquent
{
protected $fillable = [];

public function items()
{
return $this->belongsToMany('Item', 'plan_item')
->using('PlanItem')
// make sure to include the necessary foreign key in this case the `unit_id`
->withPivot('unit_id', 'qty', 'price');
}
}


// Pivot model
class PlanItem extends \Illuminate\Database\Eloquent\Relations\Pivot
{
protected $table = 'plan_item';

public function unit()
{
return $this->belongsTo('Unit');
}
}
```

From the code above, `plans` and `items` has `Many-to-Many` relationship. Each item in a plan has a selected `unit`, unit of measurement.
It also possible for other scenario that the pivot model will have other many relations.

## Eager Loading Pivot Relations

Use keyword `pivot` in eager loading pivot models. So from the example above, the pivot model `PlanItem` can eager load the `unit` relation by doing this:

```
return Plan::with('items.pivot.unit')->get();
```

The resulting data structure will be:

![image](https://cloud.githubusercontent.com/assets/4918318/17958278/0d3c962a-6acb-11e6-8415-c48d01457cd6.png)

You may also access other relations for example:

```
return Plan::with([
'items.pivot.unit',
'items.pivot.unit.someRelation',
'items.pivot.anotherRelation',
// It is also possible to eager load nested pivot models
'items.pivot.unit.someBelongsToManyRelation.pivot.anotherRelationFromAnotherPivot',
])->get();
```

## TODO/Need Help:

I would like to support customising the `pivot` keyword.
If you chain the `as()` method to define the __"pivot accessor"__ of the `BelongsToMany` relation,
it should use the defined pivot accessor.

```php
class Plan extends \Eloquent
{
protected $fillable = [];

public function items()
{
return $this->belongsToMany('Item', 'plan_item')
->withPivot('unit_id', 'qty', 'price')
->using('PlanItem')
->as('planItem');
}
}

```
So instead of using `pivot`, we can eager load it by defined pivot accessor `planItem`.

```
return Plan::with('items.planItem.unit')->get();
```
55 changes: 55 additions & 0 deletions src/EagerLoadPivotBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace AjCastro\EagerLoadPivotRelations;

use Closure;

class EagerLoadPivotBuilder extends \Illuminate\Database\Eloquent\Builder
{
/**
* Override.
* Eagerly load the relationship on a set of models.
*
* @param array $models
* @param string $name
* @param \Closure $constraints
* @return array
*/
protected function eagerLoadRelation(array $models, $name, Closure $constraints)
{
if ($name === 'pivot') {
$this->eagerLoadPivotRelations($models);
return $models;
}

return parent::eagerLoadRelation($models, $name, $constraints);
}

/**
* Eager load pivot relations.
*
* @param array $models
* @return void
*/
protected function eagerLoadPivotRelations($models)
{
$pivots = array_pluck($models, 'pivot');
$pivots = head($pivots)->newCollection($pivots);
$pivots->load($this->getPivotEagerLoadRelations());
}

/**
* Get the pivot relations to be eager loaded.
*
* @return array
*/
protected function getPivotEagerLoadRelations()
{
$relations = array_filter(array_keys($this->eagerLoad), function ($relation) {
return $relation != 'pivot' && str_contains($relation, 'pivot');
});
return array_map(function ($relation) {
return substr($relation, strlen('pivot.'));
}, $relations);
}
}
17 changes: 17 additions & 0 deletions src/EagerLoadPivotTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace AjCastro\EagerLoadPivotRelations;

trait EagerLoadPivotTrait
{
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new EagerLoadPivotBuilder($query);
}
}

0 comments on commit 67419c5

Please # to comment.