Skip to content

Instantly share code, notes, and snippets.

@ethaizone
Last active September 16, 2016 10:46
Show Gist options
  • Save ethaizone/43749da85436ae125423a0fe98991134 to your computer and use it in GitHub Desktop.
Save ethaizone/43749da85436ae125423a0fe98991134 to your computer and use it in GitHub Desktop.
Eloquent trait act as helper about relation on eloquent.
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Closure;
use Exception;
/**
* RelationHelper
*
* @author Nimit Suwannagate <[email protected]>
*/
trait RelationHelper
{
/**
* Join table with eloquent relation
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
*/
public function scopeJoinRelation($query, $relation)
{
if (empty($query->relationJoined)) {
$query->relationJoined = [];
}
$this->walkNestedRelation($relation, function($relation, $relationName, $relationDeep) use ($query) {
// Check relation is joined or not?
if (! in_array($relationName, $query->relationJoined)) {
// Create variable for join usage
$relationTable = $relation->getRelated()->getTable();
if ($relation instanceof HasOneOrMany) {
$relationForeignKey = $relation->getForeignKey();
$selfTableAndKey = $relation->getQualifiedParentKeyName();
}
if ($relation instanceof BelongsTo) {
$relationForeignKey = $relation->getQualifiedOtherKeyName();
$selfTableAndKey = $relation->getQualifiedForeignKey();
}
if ($relation instanceof HasManyThrough) {
$relationForeignKey = $relation->getForeignKey();
$selfTableAndKey = $relation->getThroughKey();
}
if (empty($selfTableAndKey)) {
throw new Exception(get_class($relation) . " is not supported. This is new relation so please implement it.");
}
// Add join to query
$query->join($relationTable, $relationForeignKey, '=', $selfTableAndKey);
// Stamp relation name to protect double join.
$query->relationJoined[] = $relationName;
}
});
}
/**
* Walk nested relation
* @param string $relation
* @param Closure $callback
* @return array
*/
public function walkNestedRelation($relation, Closure $callback)
{
$model = $this;
// Create chunk for collection relation that passed in loop.
$relationChunkPassed = [];
$results = [];
// Parse the nested relationships in a relation.
foreach (explode('.', $relation) as $relationDeep => $segment) {
// Add relation to chunk
$relationChunkPassed[] = $segment;
$relation = $model->$segment();
if (! $relation instanceof Relation) {
throw new Exception("{$segment} is not relation method.");
}
// Make nested relation from temp chunk
$relationName = implode('.', $relationChunkPassed);
// Call callback with send relation, relationName, relationDeep
$results[$relationName] = $callback($relation, $relationName, $relationDeep);
// set model with relation for next deep
$model = $relation->getRelated();
}
return $results;
}
/**
* Get relation table by nested relation
* @param string $relation
* @return string
*/
public function getRelationTable($relation)
{
$results = $this->walkNestedRelation($relation, function($relation, $relationName, $relationDeep) {
return $relation->getRelated()->getTable();
});
return last($results);
}
}
@ethaizone
Copy link
Author

ethaizone commented Sep 16, 2016

This code will make join in your query based on current relations on model. This trait support nested dot relations of Eloquent so you don't have to do stupid job to get table name or foreign key to make a good join query. Just take a look!

Example usage:

$task = app(Task::class);
$table = $task->getTable();

$tasks = $task->query()
    ->joinRelation('contactor.customer') // Support nested relation and join as chaining
    ->select([
        $table . '.id',
        DB::raw($task->getRelationTable('contactor') . '.name AS name1'),
        DB::raw($task->getRelationTable('contactor.customer') . '.name AS name2'),
        $table . '.updated_at'
    ])->get();

My models

class Task extends BaseModel
{
    public function contactor()
    {
        return $this->belongsTo(Contactor::class);
    }
}

class Contactor extends BaseModel
{
    public function customer()
    {
        return $this->belongsTo(Customer::class);
    }
}

class Customer extends BaseModel
{
}

class BaseModel extends Model
{
    use RelationHelper;
}

If you don't use my trait, this is code that you must write to do same effect.

$task = app(Task::class);
$table = $task->getTable();

$contactor = $task->contactor();

$contactorModel = $contactor->getRelated();
$contactorTable = $contactorModel->getTable();
$contactorID = $contactorModel->getKeyName();

$customer = $contactorModel->customer();
$customerModel = $customer->getRelated();
$customerTable = $customerModel->getTable();
$customerID = $customerModel->getKeyName();

$tasks = $task->query()
    // this case is for belongsTo relation
    ->join($contactorTable, $contactor->getQualifiedForeignKey(), '=', $contactor->getQualifiedOtherKeyName())
    ->join($customerTable, $customer->getQualifiedForeignKey(), '=', $customer->getQualifiedOtherKeyName())
    ->select([
        $table.'.id',
        DB::raw($contactorTable.'.name AS name1'),
        DB::raw($customerTable.'.name AS name2'),
        $table.'.updated_at'
    ])->get();

@ethaizone
Copy link
Author

I forget about support BelongsToMany. I think I will implement it later when I use it. Just don't have any model for test it right now. LOL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment