Skip to content

Commit 39d5e83

Browse files
authored
Merge pull request lazychaser#199 from ralphschindler/ancestors-relation
New Feature: AncestorsRelation that can be eagerly loaded
2 parents 16653f7 + 5adf3e0 commit 39d5e83

File tree

2 files changed

+205
-3
lines changed

2 files changed

+205
-3
lines changed

src/AncestorsRelation.php

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
namespace Kalnoy\Nestedset;
4+
5+
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
6+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Database\Eloquent\Relations\Relation;
9+
use Illuminate\Database\Query\Builder;
10+
use InvalidArgumentException;
11+
use RuntimeException;
12+
13+
class AncestorsRelation extends Relation
14+
{
15+
/**
16+
* @var QueryBuilder
17+
*/
18+
protected $query;
19+
20+
/**
21+
* @var NodeTrait|Model
22+
*/
23+
protected $parent;
24+
25+
/**
26+
* AncestorsRelation constructor.
27+
*
28+
* @param QueryBuilder $builder
29+
* @param Model $model
30+
*/
31+
public function __construct(QueryBuilder $builder, Model $model)
32+
{
33+
if ( ! NestedSet::isNode($model)) {
34+
throw new InvalidArgumentException('Model must be node.');
35+
}
36+
37+
parent::__construct($builder, $model);
38+
}
39+
40+
/**
41+
* @param EloquentBuilder $query
42+
* @param EloquentBuilder $parentQuery
43+
*
44+
* @return null
45+
*/
46+
public function getRelationExistenceCountQuery(EloquentBuilder $query, EloquentBuilder $parentQuery)
47+
{
48+
throw new RuntimeException('Cannot count ancestors, use depth functionality instead');
49+
}
50+
51+
/**
52+
* @param EloquentBuilder $query
53+
* @param EloquentBuilder $parent
54+
* @param array $columns
55+
*
56+
* @return mixed
57+
*/
58+
public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilder $parent,
59+
$columns = [ '*' ]
60+
) {
61+
$query->select($columns);
62+
63+
$table = $query->getModel()->getTable();
64+
65+
$query->from($table.' as '.$hash = $this->getRelationSubSelectHash());
66+
67+
$grammar = $query->getQuery()->getGrammar();
68+
69+
$table = $grammar->wrapTable($table);
70+
$hash = $grammar->wrapTable($hash);
71+
$parentIdName = $grammar->wrap($this->parent->getParentIdName());
72+
73+
return $query->whereRaw("{$hash}.{$parentIdName} = {$table}.{$parentIdName}");
74+
}
75+
76+
/**
77+
* @param EloquentBuilder $query
78+
* @param EloquentBuilder $parent
79+
* @param array $columns
80+
*
81+
* @return mixed
82+
*/
83+
public function getRelationQuery(
84+
EloquentBuilder $query, EloquentBuilder $parent,
85+
$columns = [ '*' ]
86+
) {
87+
return $this->getRelationExistenceQuery($query, $parent, $columns);
88+
}
89+
90+
/**
91+
* Get a relationship join table hash.
92+
*
93+
* @return string
94+
*/
95+
public function getRelationSubSelectHash()
96+
{
97+
return 'self_'.md5(microtime(true));
98+
}
99+
100+
/**
101+
* Set the base constraints on the relation query.
102+
*
103+
* @return void
104+
*/
105+
public function addConstraints()
106+
{
107+
if ( ! static::$constraints) return;
108+
109+
$this->query->whereAncestorOf($this->parent)->defaultOrder();
110+
}
111+
112+
/**
113+
* Set the constraints for an eager load of the relation.
114+
*
115+
* @param array $models
116+
*
117+
* @return void
118+
*/
119+
public function addEagerConstraints(array $models)
120+
{
121+
$model = $this->query->getModel();
122+
$table = $model->getTable();
123+
$key = $model->getKeyName();
124+
125+
$grammar = $this->query->getQuery()->getGrammar();
126+
127+
$table = $grammar->wrapTable($table);
128+
$hash = $grammar->wrap($this->getRelationSubSelectHash());
129+
$key = $grammar->wrap($key);
130+
$lft = $grammar->wrap($this->parent->getLftName());
131+
$rgt = $grammar->wrap($this->parent->getRgtName());
132+
133+
$sql = "$key IN (SELECT DISTINCT($key) FROM {$table} INNER JOIN "
134+
. "(SELECT {$lft}, {$rgt} FROM {$table} WHERE {$key} IN (" . implode(',', $this->getKeys($models))
135+
. ")) AS $hash ON {$table}.{$lft} <= {$hash}.{$lft} AND {$table}.{$rgt} >= {$hash}.{$rgt})";
136+
137+
$this->query->whereNested(function (Builder $inner) use ($sql) {
138+
$inner->whereRaw($sql);
139+
});
140+
$this->query->orderBy('lft', 'ASC');
141+
}
142+
143+
/**
144+
* Initialize the relation on a set of models.
145+
*
146+
* @param array $models
147+
* @param string $relation
148+
*
149+
* @return array
150+
*/
151+
public function initRelation(array $models, $relation)
152+
{
153+
return $models;
154+
}
155+
156+
/**
157+
* Match the eagerly loaded results to their parents.
158+
*
159+
* @param array $models
160+
* @param EloquentCollection $results
161+
* @param string $relation
162+
*
163+
* @return array
164+
*/
165+
public function match(array $models, EloquentCollection $results, $relation)
166+
{
167+
foreach ($models as $model) {
168+
$ancestors = $this->getAncestorsForModel($model, $results);
169+
170+
$model->setRelation($relation, $ancestors);
171+
}
172+
173+
return $models;
174+
}
175+
176+
/**
177+
* Get the results of the relationship.
178+
*
179+
* @return mixed
180+
*/
181+
public function getResults()
182+
{
183+
return $this->query->get();
184+
}
185+
186+
/**
187+
* @param Model $model
188+
* @param EloquentCollection $results
189+
*
190+
* @return Collection
191+
*/
192+
protected function getAncestorsForModel(Model $model, EloquentCollection $results)
193+
{
194+
$result = $this->related->newCollection();
195+
196+
foreach ($results as $ancestor) {
197+
if ($ancestor->isAncestorOf($model)) {
198+
$result->push($ancestor);
199+
}
200+
}
201+
202+
return $result;
203+
}
204+
}

src/NodeTrait.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,7 @@ public function prevNodes()
347347
*/
348348
public function ancestors()
349349
{
350-
return $this->newScopedQuery()
351-
->whereAncestorOf($this)
352-
->defaultOrder();
350+
return new AncestorsRelation($this->newScopedQuery(), $this);
353351
}
354352

355353
/**

0 commit comments

Comments
 (0)