Skip to content

Commit 5c88c2a

Browse files
committed
Fixed issues related to global scopes
1 parent 6da0552 commit 5c88c2a

File tree

3 files changed

+189
-105
lines changed

3 files changed

+189
-105
lines changed

src/Node.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Kalnoy\Nestedset;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
/**
8+
* @deprecated since 5.0
9+
*/
10+
class Node extends Model
11+
{
12+
use NodeTrait;
13+
}

src/NodeTrait.php

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,65 +1113,6 @@ public function setRgt($value)
11131113
$this->setAttribute($this->getRgtName(), $value);
11141114
}
11151115

1116-
/**
1117-
* Fixes the tree based on parentage info.
1118-
*
1119-
* Requires at least one root node. This will not update nodes with invalid parent.
1120-
*
1121-
* @return int The number of fixed nodes.
1122-
*/
1123-
public static function fixTree()
1124-
{
1125-
$model = new static;
1126-
1127-
$columns = [
1128-
$model->getKeyName(),
1129-
$model->getParentIdName(),
1130-
$model->getLftName(),
1131-
$model->getRgtName(),
1132-
];
1133-
1134-
$nodes = $model->newQuery()
1135-
->defaultOrder()
1136-
->get($columns)
1137-
->groupBy($model->getParentIdName());
1138-
1139-
self::reorderNodes($nodes, $fixed);
1140-
1141-
return $fixed;
1142-
}
1143-
1144-
/**
1145-
* @param Collection $models
1146-
* @param int $fixed
1147-
* @param $parentId
1148-
* @param int $cut
1149-
*
1150-
* @return int
1151-
*/
1152-
protected static function reorderNodes(Collection $models, &$fixed,
1153-
$parentId = null, $cut = 1
1154-
) {
1155-
/** @var Model|self $model */
1156-
foreach ($models->get($parentId, [ ]) as $model) {
1157-
$model->setLft($cut);
1158-
1159-
$cut = self::reorderNodes($models, $fixed, $model->getKey(), $cut + 1);
1160-
1161-
$model->setRgt($cut);
1162-
1163-
if ($model->isDirty()) {
1164-
$model->save();
1165-
1166-
$fixed++;
1167-
}
1168-
1169-
++$cut;
1170-
}
1171-
1172-
return $cut;
1173-
}
1174-
11751116
// public static function rebuildTree(array $nodes, $createNodes = true, $deleteNodes = false)
11761117
// {
11771118
// $model = new static;

src/QueryBuilder.php

Lines changed: 176 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ class QueryBuilder extends Builder
2929
*/
3030
public function getNodeData($id, $required = false)
3131
{
32-
$this->query->where($this->model->getKeyName(), '=', $id);
32+
$query = $this->toBase();
3333

34-
$data = $this->query->first([ $this->model->getLftName(),
35-
$this->model->getRgtName() ]);
34+
$query->where($this->model->getKeyName(), '=', $id);
35+
36+
$data = $query->first([ $this->model->getLftName(),
37+
$this->model->getRgtName() ]);
3638

3739
if ( ! $data && $required) {
3840
throw new ModelNotFoundException;
@@ -88,11 +90,13 @@ public function whereAncestorOf($id)
8890

8991
$id = $id->getKey();
9092
} else {
91-
$valueQuery = $this->model->newQuery()->getQuery()
92-
->select("_.".$this->model->getLftName())
93-
->from($this->model->getTable().' as _')
94-
->where($keyName, '=', $id)
95-
->limit(1);
93+
$valueQuery = $this->model
94+
->newQuery()
95+
->toBase()
96+
->select("_.".$this->model->getLftName())
97+
->from($this->model->getTable().' as _')
98+
->where($keyName, '=', $id)
99+
->limit(1);
96100

97101
$this->query->mergeBindings($valueQuery);
98102

@@ -247,11 +251,12 @@ protected function whereIsBeforeOrAfter($id, $operator, $boolean)
247251

248252
$this->query->addBinding($id->getLft());
249253
} else {
250-
$valueQuery = $this->model->newQuery()
251-
->getQuery()
252-
->select('_n.'.$this->model->getLftName())
253-
->from($this->model->getTable().' as _n')
254-
->where('_n.'.$this->model->getKeyName(), '=', $id);
254+
$valueQuery = $this->model
255+
->newQuery()
256+
->toBase()
257+
->select('_n.'.$this->model->getLftName())
258+
->from($this->model->getTable().' as _n')
259+
->where('_n.'.$this->model->getKeyName(), '=', $id);
255260

256261
$this->query->mergeBindings($valueQuery);
257262

@@ -306,16 +311,18 @@ public function withDepth($as = 'depth')
306311
{
307312
if ($this->query->columns === null) $this->query->columns = [ '*' ];
308313

309-
$this->query->selectSub(function (BaseQueryBuilder $q) {
310-
$table = $this->wrappedTable();
314+
$table = $this->wrappedTable();
315+
316+
list($lft, $rgt) = $this->wrappedColumns();
311317

312-
list($lft, $rgt) = $this->wrappedColumns();
318+
$query = $this->model
319+
->newQuery()
320+
->toBase()
321+
->selectRaw('count(1) - 1')
322+
->from($this->model->getTable().' as _d')
323+
->whereRaw("{$table}.{$lft} between _d.{$lft} and _d.{$rgt}");
313324

314-
$q
315-
->selectRaw('count(1) - 1')
316-
->from($this->model->getTable().' as _d')
317-
->whereRaw("{$table}.{$lft} between _d.{$lft} and _d.{$rgt}");
318-
}, $as);
325+
$this->query->selectSub($query, $as);
319326

320327
return $this;
321328
}
@@ -331,11 +338,10 @@ protected function wrappedColumns()
331338
{
332339
$grammar = $this->query->getGrammar();
333340

334-
return
335-
[
336-
$grammar->wrap($this->model->getLftName()),
337-
$grammar->wrap($this->model->getRgtName()),
338-
];
341+
return [
342+
$grammar->wrap($this->model->getLftName()),
343+
$grammar->wrap($this->model->getRgtName()),
344+
];
339345
}
340346

341347
/**
@@ -460,15 +466,21 @@ public function moveNode($key, $position)
460466
$distance = $to - $from + 1 - $height;
461467

462468
// If no distance to travel, just return
463-
if ($distance === 0) return 0;
469+
if ($distance === 0) {
470+
return 0;
471+
}
464472

465-
if ($position > $lft) $height *= -1; else $distance *= -1;
473+
if ($position > $lft) {
474+
$height *= -1;
475+
} else {
476+
$distance *= -1;
477+
}
466478

467479
$params = compact('lft', 'rgt', 'from', 'to', 'height', 'distance');
468480

469481
$boundary = [ $from, $to ];
470482

471-
$query = $this->query->where(function (Query $inner) use ($boundary) {
483+
$query = $this->toBase()->where(function (Query $inner) use ($boundary) {
472484
$inner->whereBetween($this->model->getLftName(), $boundary);
473485
$inner->orWhereBetween($this->model->getRgtName(), $boundary);
474486
});
@@ -490,12 +502,12 @@ public function makeGap($cut, $height)
490502
{
491503
$params = compact('cut', 'height');
492504

493-
$this->query->whereNested(function (Query $inner) use ($cut) {
505+
$query = $this->toBase()->whereNested(function (Query $inner) use ($cut) {
494506
$inner->where($this->model->getLftName(), '>=', $cut);
495507
$inner->orWhere($this->model->getRgtName(), '>=', $cut);
496508
});
497509

498-
return $this->query->update($this->patch($params));
510+
return $query->update($this->patch($params));
499511
}
500512

501513
/**
@@ -511,7 +523,7 @@ protected function patch(array $params)
511523
{
512524
$grammar = $this->query->getGrammar();
513525

514-
$columns = array();
526+
$columns = [];
515527

516528
foreach ([ $this->model->getLftName(), $this->model->getRgtName() ] as $col) {
517529
$columns[$col] = $this->columnPatch($grammar->wrap($col), $params);
@@ -564,31 +576,149 @@ protected function columnPatch($col, array $params)
564576
*/
565577
public function countErrors()
566578
{
567-
$table = $this->wrappedTable();
568-
list($lft, $rgt) = $this->wrappedColumns();
569-
570-
$checks = array();
579+
$checks = [];
571580

572581
// Check if lft and rgt values are ok
573-
$checks['oddness'] = "from {$table} where {$lft} >= {$rgt} or ({$rgt} - {$lft}) % 2 = 0";
582+
$checks['oddness'] = $this->getOdnessQuery();
574583

575584
// Check if lft and rgt values are unique
576-
$checks['duplicates'] = "from {$table} c1, {$table} c2 where c1.id <> c2.id and ".
577-
"(c1.{$lft}=c2.{$lft} or c1.{$rgt}=c2.{$rgt} or c1.{$lft}=c2.{$rgt} or c1.{$rgt}=c2.{$lft})";
585+
$checks['duplicates'] = $this->getDuplicatesQuery();
578586

579587
// Check if parent_id is set correctly
580-
$checks['wrong_parent'] =
581-
"from {$table} c, {$table} p, $table m ".
582-
"where c.parent_id=p.id and m.id <> p.id and m.id <> c.id and ".
583-
"(c.{$lft} not between p.{$lft} and p.{$rgt} or c.{$lft} between m.{$lft} and m.{$rgt} and m.{$lft} between p.{$lft} and p.{$rgt})";
588+
$checks['wrong_parent'] = $this->getWrongParentQuery();
584589

585-
$query = $this->query->newQuery();
590+
$query = $this->toBase();
586591

587-
foreach ($checks as $key => $check) {
588-
$query->addSelect(new Expression('(select count(1) '.$check.') as '.$key));
592+
foreach ($checks as $key => $inner) {
593+
$inner->selectRaw('count(1)');
594+
595+
$query->selectSub($inner, $key);
589596
}
590597

591598
return (array)$query->first();
592599
}
593600

601+
/**
602+
* @return BaseQueryBuilder
603+
*/
604+
protected function getOdnessQuery()
605+
{
606+
return $this->model
607+
->newServiceQuery()
608+
->toBase()
609+
->whereNested(function (BaseQueryBuilder $inner) {
610+
list($lft, $rgt) = $this->wrappedColumns();
611+
612+
$inner->whereRaw("{$lft} >= {$rgt}")
613+
->orWhereRaw("({$rgt} - {$lft}) % 2 = 0");
614+
});
615+
}
616+
617+
/**
618+
* @return BaseQueryBuilder
619+
*/
620+
protected function getDuplicatesQuery()
621+
{
622+
$table = $this->wrappedTable();
623+
624+
return $this->model
625+
->newServiceQuery()
626+
->toBase()
627+
->from($this->query->raw("{$table} c1, {$table} c2"))
628+
->whereRaw("c1.id <> c2.id")
629+
->whereNested(function (BaseQueryBuilder $inner) {
630+
list($lft, $rgt) = $this->wrappedColumns();
631+
632+
$inner->orWhereRaw("c1.{$lft}=c2.{$lft}")
633+
->orWhereRaw("c1.{$rgt}=c2.{$rgt}")
634+
->orWhereRaw("c1.{$lft}=c2.{$rgt}")
635+
->orWhereRaw("c1.{$rgt}=c2.{$lft}");
636+
});
637+
}
638+
639+
/**
640+
* @return BaseQueryBuilder
641+
*/
642+
protected function getWrongParentQuery()
643+
{
644+
$table = $this->wrappedTable();
645+
$keyName = $this->wrappedKey();
646+
$parentIdName = $this->query->raw($this->model->getParentIdName());
647+
648+
return $this->model
649+
->newServiceQuery()
650+
->toBase()
651+
->from($this->query->raw("{$table} c, {$table} p, $table m"))
652+
->whereRaw("c.{$parentIdName}=p.{$keyName}")
653+
->whereRaw("m.{$keyName} <> p.{$keyName}")
654+
->whereRaw("m.{$keyName} <> c.{$keyName}")
655+
->whereNested(function (BaseQueryBuilder $inner) {
656+
list($lft, $rgt) = $this->wrappedColumns();
657+
658+
$inner->whereRaw("c.{$lft} not between p.{$lft} and p.{$rgt}")
659+
->orWhereRaw("c.{$lft} between m.{$lft} and m.{$rgt}")
660+
->whereRaw("m.{$lft} between p.{$lft} and p.{$rgt}");
661+
});
662+
663+
}
664+
665+
/**
666+
* Fixes the tree based on parentage info.
667+
*
668+
* Requires at least one root node. This will not update nodes with invalid parent.
669+
*
670+
* @return int The number of fixed nodes.
671+
*/
672+
public function fixTree()
673+
{
674+
$columns = [
675+
$this->model->getKeyName(),
676+
$this->model->getParentIdName(),
677+
$this->model->getLftName(),
678+
$this->model->getRgtName(),
679+
];
680+
681+
$nodes = $this->model
682+
->newQuery()
683+
->defaultOrder()
684+
->get($columns)
685+
->groupBy($this->model->getParentIdName());
686+
687+
$fixed = 0;
688+
689+
self::reorderNodes($nodes, $fixed);
690+
691+
return $fixed;
692+
}
693+
694+
/**
695+
* @param Collection $models
696+
* @param int $fixed
697+
* @param $parentId
698+
* @param int $cut
699+
*
700+
* @return int
701+
*/
702+
protected static function reorderNodes(Collection $models, &$fixed,
703+
$parentId = null, $cut = 1
704+
) {
705+
/** @var Model|self $model */
706+
foreach ($models->get($parentId, [ ]) as $model) {
707+
$model->setLft($cut);
708+
709+
$cut = self::reorderNodes($models, $fixed, $model->getKey(), $cut + 1);
710+
711+
$model->setRgt($cut);
712+
713+
if ($model->isDirty()) {
714+
$model->save();
715+
716+
$fixed++;
717+
}
718+
719+
++$cut;
720+
}
721+
722+
return $cut;
723+
}
594724
}

0 commit comments

Comments
 (0)