Issues (34)

src/SetBuilder.php (6 issues)

1
<?php
2
3
namespace Encima\Albero;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Encima\Albero\Extensions\Eloquent\Collection;
7
8
class SetBuilder
9
{
10
    /** @var \Illuminate\Database\Eloquent\Model */
11
    protected $node = null;
12
13
    protected array $bounds = [];
14
15
    public function __construct(Model $node)
16
    {
17
        $this->node = $node;
18
    }
19
20
    /**
21
     * Perform the re-calculation of the left and right indexes of the whole
22
     * nested set tree structure.
23
     *
24
     * @param  bool $force
25
     * @return void
26
     */
27
    public function rebuild(bool $force = false): void
28
    {
29
        $alreadyValid = forward_static_call([get_class($this->node), 'isValidNestedSet']);
30
31
        // Do not rebuild a valid Nested Set tree structure
32
        if (!$force && $alreadyValid) {
33
            return;
34
        }
35
36
        // Rebuild lefts and rights for each root node and its children (recursively).
37
        // We go by setting left (and keep track of the current left bound), then
38
        // search for each children and recursively set the left index (while
39
        // incrementing that index). When going back up the recursive chain we start
40
        // setting the right indexes and saving the nodes...
41
        $self = $this;
42
43
        $this->node->getConnection()->transaction(function () use ($self) {
44
            foreach ($self->roots() as $root) {
45
                $self->rebuildBounds($root, 0);
46
            }
47
        });
48
    }
49
50
    /**
51
     * Return all root nodes for the current database table appropiately sorted.
52
     *
53
     * @return Illuminate\Database\Eloquent\Collection
0 ignored issues
show
The type Encima\Albero\Illuminate...ase\Eloquent\Collection was not found. Did you mean Illuminate\Database\Eloquent\Collection? If so, make sure to prefix the type with \.
Loading history...
54
     */
55
    public function roots(): Collection
56
    {
57
        return $this->node->newQuery()
58
            ->whereNull($this->node->getQualifiedParentColumnName())
59
            ->orderBy($this->node->getQualifiedLeftColumnName())
60
            ->orderBy($this->node->getQualifiedRightColumnName())
61
            ->orderBy($this->node->getQualifiedKeyName())
62
            ->get();
63
    }
64
65
    /**
66
     * Recompute left and right index bounds for the specified node and its
67
     * children (recursive call). Fill the depth column too.
68
     */
69
    public function rebuildBounds(Model $node, int $depth = 0)
70
    {
71
        $k = $this->scopedKey($node);
72
73
        $node->setAttribute($node->getLeftColumnName(), $this->getNextBound($k));
0 ignored issues
show
It seems like $node->getLeftColumnName() can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $key of Illuminate\Database\Eloquent\Model::setAttribute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

73
        $node->setAttribute(/** @scrutinizer ignore-type */ $node->getLeftColumnName(), $this->getNextBound($k));
Loading history...
74
        $node->setAttribute($node->getDepthColumnName(), $depth);
75
76
        foreach ($this->children($node) as $child) {
77
            $this->rebuildBounds($child, $depth + 1);
78
        }
79
80
        $node->setAttribute($node->getRightColumnName(), $this->getNextBound($k));
81
82
        $node->save();
83
    }
84
85
    /**
86
     * Return all children for the specified node.
87
     *
88
     * @param   Baum\Node $node
0 ignored issues
show
The type Encima\Albero\Baum\Node was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
89
     * @return  Illuminate\Database\Eloquent\Collection
90
     */
91
    public function children(Model $node): Collection
92
    {
93
        $query = $this->node->newQuery();
94
95
        $query->where($this->node->getQualifiedParentColumnName(), '=', $node->getKey());
96
97
        // We must also add the scoped column values to the query to compute valid
98
        // left and right indexes.
99
        foreach ($this->scopedAttributes($node) as $fld => $value) {
100
            $query->where($this->qualify($fld), '=', $value);
101
        }
102
103
        $query->orderBy($this->node->getQualifiedLeftColumnName());
104
        $query->orderBy($this->node->getQualifiedRightColumnName());
105
        $query->orderBy($this->node->getQualifiedKeyName());
106
107
        return $query->get();
108
    }
109
110
    /**
111
     * Return an array of the scoped attributes of the supplied node.
112
     *
113
     * @param   Baum\Node $node
114
     * @return  array
115
     */
116
    protected function scopedAttributes(Model $node): array
117
    {
118
        $keys = $this->node->getScopedColumns();
119
120
        if (count($keys) == 0) {
0 ignored issues
show
It seems like $keys can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
        if (count(/** @scrutinizer ignore-type */ $keys) == 0) {
Loading history...
121
            return [];
122
        }
123
124
        $values = array_map(function ($column) use ($node) {
125
            return $node->getAttribute($column);
126
        }, $keys);
0 ignored issues
show
It seems like $keys can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

126
        }, /** @scrutinizer ignore-type */ $keys);
Loading history...
127
128
        return array_combine($keys, $values);
0 ignored issues
show
It seems like $keys can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $keys of array_combine() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
        return array_combine(/** @scrutinizer ignore-type */ $keys, $values);
Loading history...
129
    }
130
131
    /**
132
     * Return a string-key for the current scoped attributes. Used for index
133
     * computing when a scope is defined (acsts as an scope identifier).
134
     *
135
     * @param   Baum\Node $node
136
     * @return  string
137
     */
138
    protected function scopedKey(Model $node): string
139
    {
140
        $attributes = $this->scopedAttributes($node);
141
142
        $output = [];
143
144
        foreach ($attributes as $fld => $value) {
145
            $output[] = $this->qualify($fld).'='.(is_null($value) ? 'NULL' : $value);
146
        }
147
148
        // NOTE: Maybe an md5 or something would be better. Should be unique though.
149
        return implode(',', $output);
150
    }
151
152
    /**
153
     * Return next index bound value for the given key (current scope identifier)
154
     *
155
     * @param   string  $key
156
     * @return  integer
157
     */
158
    protected function getNextBound(string $key): int
159
    {
160
        if (false === array_key_exists($key, $this->bounds)) {
161
            $this->bounds[$key] = 0;
162
        }
163
164
        $this->bounds[$key] = $this->bounds[$key] + 1;
165
166
        return $this->bounds[$key];
167
    }
168
169
    /**
170
     * Get the fully qualified value for the specified column.
171
     *
172
     * @return string
173
     */
174
    protected function qualify(string $column): string
175
    {
176
        return $this->node->getTable().'.'.$column;
177
    }
178
}
179