Skip to content

Commit

Permalink
Added belongsToTree relation (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zlob authored and pvsaintpe committed Oct 25, 2019
1 parent 5cb6cb1 commit 1e7f172
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 166 deletions.
116 changes: 0 additions & 116 deletions src/Relations/BelongsToLevel.php

This file was deleted.

138 changes: 138 additions & 0 deletions src/Relations/BelongsToTree.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

declare(strict_types=1);

namespace Umbrellio\LTree\Relations;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\JoinClause;
use Umbrellio\LTree\Interfaces\LTreeModelInterface;

class BelongsToTree extends Relation
{
protected $throughRelationName;
private $foreignKey;
private $ownerKey;

public function __construct(
Builder $query,
Model $child,
string $throughRelationName,
string $foreignKey,
string $ownerKey
) {
$this->throughRelationName = $throughRelationName;
$this->foreignKey = $foreignKey;
$this->ownerKey = $ownerKey;

parent::__construct($query, $child);
}

public function addConstraints(): void
{
if (static::$constraints) {
$relation = $this->parent->{$this->throughRelationName};

if ($relation) {
$this->query = $relation->newQuery()->ancestorsOf($relation)->orderBy('path');
}
}
}

public function addEagerConstraints(array $models): void
{
$key = $this->related->getTable() . '.' . $this->ownerKey;

$whereIn = $this->whereInMethod($this->related, $this->ownerKey);

$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));

$table = $this->getModel()->getTable();
$alias = sprintf('%s_depends', $table);

$this->query->join(
sprintf('%s as %s', $table, $alias),
function (JoinClause $query) use ($alias, $table) {
/** @var LTreeModelInterface $related */
$related = $this->parent->{$this->throughRelationName}()->related;

$query->whereRaw(sprintf(
'%1$s.%2$s @> %3$s.%2$s',
$alias,
$related->getLtreePathColumn(),
$table
));
}
);

$this->query->orderBy('path');

$this->query->selectRaw(sprintf('%s.*, %s.%s as relation_id', $alias, $table, $this->ownerKey));
}

public function match(array $models, Collection $results, $relation)
{
$dictionary = [];

foreach ($results as $result) {
$dictionary[$result->relation_id][] = $result;
}

foreach ($models as $model) {
foreach ($dictionary as $related => $value) {
if ($model->getAttribute($this->foreignKey) === $related) {
$model->setRelation($relation, $this->related->newCollection($value));
}
}
}

return $models;
}

public function getResults()
{
return $this->getParentKey() !== null
? $this->query->get()
: $this->related->newCollection();
}


/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->related->newCollection());
}

return $models;
}

protected function getEagerModelKeys(array $models)
{
$keys = [];

foreach ($models as $model) {
if (($value = $model->{$this->foreignKey}) !== null) {
$keys[] = $value;
}
}

sort($keys);

return array_values(array_unique($keys));
}

private function getParentKey()
{
return $this->parent->{$this->foreignKey};
}
}
26 changes: 5 additions & 21 deletions src/Traits/HasTreeRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Illuminate\Database\Eloquent\Model;
use Umbrellio\LTree\Exceptions\InvalidTraitInjectionClass;
use Umbrellio\LTree\Interfaces\LTreeModelInterface;
use Umbrellio\LTree\Relations\BelongsToLevel;
use Umbrellio\LTree\Relations\BelongsToTree;

/**
* @mixin HasRelationships
Expand All @@ -20,26 +20,18 @@ trait HasTreeRelationships
/**
* @param string $related
* @param string $throwRelation
* @param int $level
* @param string|null $foreignKey
* @param null $ownerKey
* @param string|null $relation
* @return BelongsToLevel
* @return BelongsToTree
*
* @throws InvalidTraitInjectionClass
*/
protected function belongsToLevel(
protected function belongsToTree(
string $related,
string $throwRelation,
int $level = 1,
?string $foreignKey = null,
$ownerKey = null,
?string $relation = null
$ownerKey = null
) {
if ($relation === null) {
$relation = $this->guessBelongsToRelation();
}

$instance = $this->newRelatedInstance($related);

if (!$instance instanceof LTreeModelInterface) {
Expand All @@ -55,14 +47,6 @@ protected function belongsToLevel(

$ownerKey = $ownerKey ?: $instance->getKeyName();

return new BelongsToLevel(
$instance->newQuery(),
$this,
$throwRelation,
$level,
$foreignKey,
$ownerKey,
$relation
);
return new BelongsToTree($instance->newQuery(), $this, $throwRelation, $foreignKey, $ownerKey);
}
}
33 changes: 16 additions & 17 deletions tests/Functional/LtreeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,18 @@ public function root(): void
}
}

public function provideBelongsLevels(): Generator
public function provideBelongsTree(): Generator
{
yield 'two_levels' => [
'category_id' => 3,
'count' => 2,
'expected1' => 1,
'expected2' => 3,
'expected3' => null,
];
yield 'three_levels' => [
'category_id' => 6,
'count' => 3,
'expected1' => 1,
'expected2' => 3,
'expected3' => 6,
Expand All @@ -179,9 +181,9 @@ public function provideBelongsLevels(): Generator

/**
* @test
* @dataProvider provideBelongsLevels
* @dataProvider provideBelongsTree
*/
public function getBelongsToLevel($id, $level1, $level2, $level3)
public function getBelongsToTree($id, $count, $level1, $level2, $level3)
{
$tree = $this->createTreeNodes($this->getTreeNodes());
$product = $this->getProduct();
Expand All @@ -190,20 +192,17 @@ public function getBelongsToLevel($id, $level1, $level2, $level3)
$product->save();

$find = ProductStub::first();
$this->assertFalse(array_key_exists('category1', $find->toArray()));
$this->assertFalse(array_key_exists('category2', $find->toArray()));
$this->assertFalse(array_key_exists('category3', $find->toArray()));
$this->assertSame($level1, optional($find->category1)->id);
$this->assertSame($level2, optional($find->category2)->id);
$this->assertSame($level3, optional($find->category3)->id);

$find = ProductStub::with(['category1', 'category2', 'category3'])->first();
$this->assertTrue(array_key_exists('category1', $find->toArray()));
$this->assertTrue(array_key_exists('category2', $find->toArray()));
$this->assertTrue(array_key_exists('category3', $find->toArray()));
$this->assertSame($level1, optional($find->category1)->id);
$this->assertSame($level2, optional($find->category2)->id);
$this->assertSame($level3, optional($find->category3)->id);
$this->assertFalse(array_key_exists('category_tree', $find->toArray()));
$this->assertSame($level1, optional($find->categoryTree->get(0))->id);
$this->assertSame($level2, optional($find->categoryTree->get(1))->id);
$this->assertSame($level3, optional($find->categoryTree->get(2))->id);

$find = ProductStub::with('categoryTree')->first();
$this->assertTrue(array_key_exists('category_tree', $find->toArray()));
$this->assertSame($count, $find->categoryTree->count());
$this->assertSame($level1, optional($find->categoryTree->get(0))->id);
$this->assertSame($level2, optional($find->categoryTree->get(1))->id);
$this->assertSame($level3, optional($find->categoryTree->get(2))->id);
}

/** @test */
Expand Down
14 changes: 2 additions & 12 deletions tests/Functional/ProductStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,8 @@ public function category()
return $this->belongsTo(CategoryStub::class);
}

public function category1()
public function categoryTree()
{
return $this->belongsToLevel(CategoryStub::class, 'category', 1);
}

public function category2()
{
return $this->belongsToLevel(CategoryStub::class, 'category', 2, 'category_id', 'id', 'category2');
}

public function category3()
{
return $this->belongsToLevel(CategoryStub::class, 'category', 3);
return $this->belongsToTree(CategoryStub::class, 'category');
}
}

0 comments on commit 1e7f172

Please sign in to comment.