Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.29% covered (success)
94.29%
33 / 35
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
FixTreeCommand
94.29% covered (success)
94.29%
33 / 35
50.00% covered (danger)
50.00%
1 / 2
6.01
0.00% covered (danger)
0.00%
0 / 1
 handle
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 rebuildClosures
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3// @todo make agnostic
4
5namespace Baril\Bonsai\Console;
6
7use Illuminate\Console\Command;
8use Illuminate\Database\Eloquent\Model;
9
10class FixTreeCommand extends Command
11{
12    protected $signature = 'bonsai:fix {model : The model class.}';
13    protected $description = 'Rebuilds the closures for a given tree';
14
15    public function handle()
16    {
17        $model = $this->input->getArgument('model');
18        if (
19            !class_exists($model)
20            || !is_subclass_of($model, Model::class)
21            || !method_exists($model, 'getClosureTable')
22        ) {
23            $this->error('{model} must be a valid model class and use the BelongsToTree trait!');
24            return;
25        }
26
27        $this->rebuildClosures($model);
28    }
29
30    protected function rebuildClosures($model)
31    {
32        $instance = new $model();
33        $connection = $instance->getConnection();
34        $connection->transaction(function () use ($instance, $connection) {
35            $table = $instance->getTable();
36            $parentKey = $instance->getParentForeignKeyName();
37            $primaryKey = $instance->getKeyName();
38            $closureTable = $instance->getClosureTable();
39
40            // Delete old closures:
41            $connection->table($closureTable)->delete();
42
43            // Insert "self-closures":
44            $select = $connection->table($table)->select($primaryKey, $primaryKey, $connection->raw('0'));
45            $connection->table($closureTable)->insertUsing(['ancestor_id', 'descendant_id', 'depth'], $select);
46
47            // Increment depth and insert closures until there's nothing left to insert:
48            $depth = 1;
49            $continue = true;
50            while ($continue) {
51                // INSERT INTO $closureTable (ancestor_id, descendant_id, depth)
52                // SELECT closure_table.ancestor_id, main_table.$primaryKey, $depth
53                // FROM $table AS main_table
54                // INNER JOIN $closureTable AS closure_table
55                //     ON main_table.$parentKey = closure_table.descendant_id
56                // WHERE closure_table.depth = $depth - 1"
57                $select = $connection
58                    ->table($table, 'main_table')
59                    ->join(
60                        "$closureTable as closure_table",
61                        "main_table.$parentKey",
62                        '=',
63                        'closure_table.descendant_id'
64                    )
65                    ->where('closure_table.depth', '=', $depth - 1)
66                    ->select('closure_table.ancestor_id', "main_table.$primaryKey", $connection->raw((string) $depth));
67                $connection->table($closureTable)->insertUsing(['ancestor_id', 'descendant_id', 'depth'], $select);
68
69                $continue = (bool) $connection->table($closureTable)->where('depth', '=', $depth)->exists();
70                $depth++;
71            }
72        });
73
74        $this->line("<info>Rebuilt the closures for:</info> $model");
75    }
76}