1: <?php
2:
3: // @todo make agnostic
4:
5: namespace Baril\Bonsai\Console;
6:
7: use Illuminate\Console\Command;
8: use Illuminate\Database\Eloquent\Model;
9:
10: class 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: }
77: