Skip to content

Commit 6e229b6

Browse files
authored
feat: introduce the depth attribute (#281)
1 parent 10ca0c6 commit 6e229b6

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

‎src/Entity.php‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Database\Eloquent\Model as Eloquent;
88
use Illuminate\Database\Eloquent\Relations\BelongsTo;
99
use Illuminate\Database\Eloquent\Relations\HasMany;
10+
use Illuminate\Database\Eloquent\Relations\HasOne;
1011
use Illuminate\Database\Eloquent\SoftDeletes;
1112
use Franzose\ClosureTable\Scope\Ancestor;
1213
use Franzose\ClosureTable\Scope\DirectChild;
@@ -23,8 +24,9 @@
2324
* However, if you named, for example, the position column to be "pos",
2425
* remember you can get its value either by $this->pos or $this->position.
2526
*
26-
* @property int position Alias for the current position attribute name
27+
* @property int|null position Alias for the current position attribute name
2728
* @property mixed parent_id Alias for the direct ancestor identifier attribute name
29+
* @property int|null depth Current node depth
2830
* @property EntityCollection children Child nodes loaded from the database
2931
*/
3032
class Entity extends Eloquent
@@ -35,6 +37,7 @@ class Entity extends Eloquent
3537
use DirectChild;
3638
use Sibling;
3739

40+
private const DEPTH_RELATION_NAME = 'closureDepth';
3841
public const CHILDREN_RELATION_NAME = 'children';
3942

4043
/**
@@ -69,6 +72,15 @@ class Entity extends Eloquent
6972
*/
7073
public $timestamps = false;
7174

75+
/**
76+
* The relations to eager load on every query.
77+
*
78+
* @var string[]
79+
*/
80+
protected $with = [
81+
self::DEPTH_RELATION_NAME,
82+
];
83+
7284
/**
7385
* Entity constructor.
7486
*/
@@ -182,6 +194,42 @@ public function getPositionColumn(): string
182194
return 'position';
183195
}
184196

197+
/**
198+
* Returns the closure row with the max depth for the current node.
199+
*
200+
* @internal
201+
*/
202+
public function closureDepth(): HasOne
203+
{
204+
$relatedClass = get_class($this->closure);
205+
$descendantColumn = $this->closure->getDescendantColumn();
206+
$depthColumn = $this->closure->getDepthColumn();
207+
208+
$relation = $this->hasOne($relatedClass, $descendantColumn, $this->getKeyName());
209+
$relation->getRelated()->setTable($this->closure->getTable());
210+
211+
return $relation->ofMany($depthColumn, 'max');
212+
}
213+
214+
/**
215+
* Gets the current node depth.
216+
*/
217+
public function getDepthAttribute(): ?int
218+
{
219+
if (!$this->exists) {
220+
return null;
221+
}
222+
223+
if ($this->relationLoaded(static::DEPTH_RELATION_NAME)) {
224+
$depth = $this->getRelation(static::DEPTH_RELATION_NAME)?->getAttribute($this->closure->getDepthColumn());
225+
return $depth !== null ? (int) $depth : 0;
226+
}
227+
228+
$depth = $this->closureDepth()->value($this->closure->getDepthColumn());
229+
230+
return $depth !== null ? (int) $depth : 0;
231+
}
232+
185233
/**
186234
* Checks if the current model table has the given column.
187235
*/

‎tests/Entity/DepthTests.php‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Franzose\ClosureTable\Tests\Entity;
5+
6+
use Franzose\ClosureTable\Entity;
7+
use Franzose\ClosureTable\Tests\BaseTestCase;
8+
9+
class DepthTests extends BaseTestCase
10+
{
11+
public function testDepthAccessorUsesEagerLoadedRelation(): void
12+
{
13+
$expected = [
14+
9 => 0,
15+
10 => 1,
16+
11 => 2,
17+
12 => 3,
18+
13 => 1,
19+
14 => 1,
20+
15 => 1,
21+
];
22+
23+
$entities = Entity::whereIn('id', array_keys($expected))->get();
24+
25+
foreach ($expected as $id => $depth) {
26+
$entity = $entities->firstWhere('id', $id);
27+
28+
static::assertNotNull($entity);
29+
static::assertTrue($entity->relationLoaded('closureDepth'));
30+
static::assertEquals($depth, $entity->depth);
31+
}
32+
}
33+
34+
public function testDepthUpdatesAfterMove(): void
35+
{
36+
$entity = Entity::find(12);
37+
$entity->moveTo(0, 13);
38+
39+
$entity = Entity::find(12);
40+
41+
static::assertEquals(2, $entity->depth);
42+
}
43+
44+
public function testDepthForCreatedEntityWithoutRelationLoaded(): void
45+
{
46+
$entity = Entity::create(['title' => 'abcde']);
47+
48+
static::assertFalse($entity->relationLoaded('closureDepth'));
49+
static::assertEquals(0, $entity->depth);
50+
}
51+
52+
public function testDepthForUnsavedEntityIsNull(): void
53+
{
54+
static::assertNull((new Entity())->depth);
55+
}
56+
}

0 commit comments

Comments
 (0)