Skip to content

Commit

Permalink
Closes area17#600: Improve tree reorder algorithm for nested modules
Browse files Browse the repository at this point in the history
The idea here was to reduce the number of times the script needs to
interact with the database. With this new code, there should be fewer
`save()`, `saveAsRoot()`, and `self::find()` calls happening. The
trade-off is that there is now an `all()` call, which could cause
issues if there is truly a massive amount of items (thousands). But
for most use-cases, this new algorithm should behave better than the
old one when there is a middling number of items (hundreds).
  • Loading branch information
IllyaMoskvin authored and ifox committed Jul 6, 2020
1 parent 12acbd6 commit c00bf01
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 64 deletions.
63 changes: 34 additions & 29 deletions docs/.sections/crud-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,43 +708,48 @@ use Kalnoy\Nestedset\NodeTrait;
class Page extends Model {
use HasPosition, NodeTrait;
...
public static function saveTreeFromIds($nodesArray)
public static function saveTreeFromIds($nodeTree)
{
$parentNodes = self::find(Arr::pluck($nodesArray, 'id'));

if (is_array($nodesArray)) {
$position = 1;
foreach ($nodesArray as $nodeArray) {
$node = $parentNodes->where('id', $nodeArray['id'])->first();
$node->position = $position++;
$node->saveAsRoot();
}
}
$nodeModels = self::all();
$nodeArrays = self::flattenTree($nodeTree);

$parentNodes = self::find(Arr::pluck($nodesArray, 'id'));
foreach ($nodeArrays as $nodeArray) {
$nodeModel = $nodeModels->where('id', $nodeArray['id'])->first();

self::rebuildTree($nodesArray, $parentNodes);
if ($nodeArray['parent_id'] === null) {
if (!$nodeModel->isRoot() || $nodeModel->position !== $nodeArray['position']) {
$nodeModel->position = $nodeArray['position'];
$nodeModel->saveAsRoot();
}
} else {
if ($nodeModel->position !== $nodeArray['position'] || $nodeModel->parent_id !== $nodeArray['parent_id']) {
$nodeModel->position = $nodeArray['position'];
$nodeModel->parent_id = $nodeArray['parent_id'];
$nodeModel->save();
}
}
}
}

public static function rebuildTree($nodesArray, $parentNodes)
public static function flattenTree(array $nodeTree, int $parentId = null)
{
if (is_array($nodesArray)) {
foreach ($nodesArray as $nodeArray) {
$parent = $parentNodes->where('id', $nodeArray['id'])->first();
if (isset($nodeArray['children']) && is_array($nodeArray['children'])) {
$position = 1;
$nodes = self::find(Arr::pluck($nodeArray['children'], 'id'));
foreach ($nodeArray['children'] as $child) {
//append the children to their (old/new)parents
$descendant = $nodes->where('id', $child['id'])->first();
$descendant->position = $position++;
$descendant->parent_id = $parent->id;
$descendant->save();
self::rebuildTree($nodeArray['children'], $nodes);
}
}
$nodeArrays = [];
$position = 0;

foreach ($nodeTree as $node) {
$nodeArrays[] = [
'id' => $node['id'],
'position' => $position++,
'parent_id' => $parentId,
];

if (count($node['children']) > 0) {
$childArrays = self::flattenTree($node['children'], $node['id']);
$nodeArrays = array_merge($nodeArrays, $childArrays);
}
}

return $nodeArrays;
}
}
```
Expand Down
68 changes: 33 additions & 35 deletions tests/stubs/modules/categories/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,49 +30,47 @@ class Category extends Model
// add checkbox fields names here (published toggle is itself a checkbox)
public $checkboxes = ['published'];

public static function saveTreeFromIds($nodesArray)
public static function saveTreeFromIds($nodeTree)
{
$parentNodes = self::find(array_pluck($nodesArray, 'id'));
$nodeModels = self::all();
$nodeArrays = self::flattenTree($nodeTree);

if (is_array($nodesArray)) {
$position = 1;
foreach ($nodesArray as $nodeArray) {
$node = $parentNodes->where('id', $nodeArray['id'])->first();
$node->position = $position++;
$node->saveAsRoot();
foreach ($nodeArrays as $nodeArray) {
$nodeModel = $nodeModels->where('id', $nodeArray['id'])->first();

if ($nodeArray['parent_id'] === null) {
if (!$nodeModel->isRoot() || $nodeModel->position !== $nodeArray['position']) {
$nodeModel->position = $nodeArray['position'];
$nodeModel->saveAsRoot();
}
} else {
if ($nodeModel->position !== $nodeArray['position'] || $nodeModel->parent_id !== $nodeArray['parent_id']) {
$nodeModel->position = $nodeArray['position'];
$nodeModel->parent_id = $nodeArray['parent_id'];
$nodeModel->save();
}
}
}

$parentNodes = self::find(array_pluck($nodesArray, 'id'));

self::rebuildTree($nodesArray, $parentNodes);
}

public static function rebuildTree($nodesArray, $parentNodes)
public static function flattenTree(array $nodeTree, int $parentId = null)
{
if (is_array($nodesArray)) {
foreach ($nodesArray as $nodeArray) {
$parent = $parentNodes->where('id', $nodeArray['id'])->first();
if (
isset($nodeArray['children']) &&
is_array($nodeArray['children'])
) {
$position = 1;
$nodes = self::find(
array_pluck($nodeArray['children'], 'id')
);
foreach ($nodeArray['children'] as $child) {
//append the children to their (old/new)parents
$descendant = $nodes
->where('id', $child['id'])
->first();
$descendant->position = $position++;
$descendant->parent_id = $parent->id;
$descendant->save();
self::rebuildTree($nodeArray['children'], $nodes);
}
}
$nodeArrays = [];
$position = 0;

foreach ($nodeTree as $node) {
$nodeArrays[] = [
'id' => $node['id'],
'position' => $position++,
'parent_id' => $parentId,
];

if (count($node['children']) > 0) {
$childArrays = self::flattenTree($node['children'], $node['id']);
$nodeArrays = array_merge($nodeArrays, $childArrays);
}
}

return $nodeArrays;
}
}

0 comments on commit c00bf01

Please sign in to comment.