SetMapper::map()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 12
rs 10
1
<?php
2
3
namespace Encima\Albero;
4
5
use Closure;
6
use Illuminate\Support\Arr;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Builder;
9
use Illuminate\Support\Contracts\ArrayableInterface;
0 ignored issues
show
Bug introduced by
The type Illuminate\Support\Contracts\ArrayableInterface 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...
10
11
class SetMapper
12
{
13
    /** @var \Illuminate\Database\Eloquent\Model */
14
    protected $node = null;
15
16
    protected string $childrenKeyName = 'children';
17
18
    /**
19
     * Create a new \Baum\SetBuilder class instance.
20
     *
21
     * @param   \Baum\Node      $node
0 ignored issues
show
Bug introduced by
The type 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...
22
     * @return  void
23
     */
24
    public function __construct(Model $node, string $childrenKeyName = 'children')
25
    {
26
        $this->node = $node;
27
28
        $this->childrenKeyName = $childrenKeyName;
29
    }
30
31
    /**
32
     * Maps a tree structure into the database. Unguards & wraps in transaction.
33
     *
34
     * @param   array|\Illuminate\Support\Contracts\ArrayableInterface
35
     * @return  boolean
36
     */
37
    public function map($nodeList): bool
38
    {
39
        $self = $this;
40
41
        return $this->wrapInTransaction(function () use ($self, $nodeList) {
42
            forward_static_call([get_class($self->node), 'unguard']);
43
44
            $result = $self->mapTree($nodeList);
45
46
            forward_static_call([get_class($self->node), 'reguard']);
47
48
            return $result;
49
        });
50
    }
51
52
    /**
53
     * Maps a tree structure into the database without unguarding nor wrapping
54
     * inside a transaction.
55
     *
56
     * @param   array|\Illuminate\Support\Contracts\ArrayableInterface
57
     * @return  boolean
58
     */
59
    public function mapTree($nodeList): bool
60
    {
61
        $tree = $nodeList instanceof ArrayableInterface ? $nodeList->toArray() : $nodeList;
62
63
        $affectedKeys = [];
64
65
        $result = $this->mapTreeRecursive($tree, $this->node->getKey(), $affectedKeys);
66
67
        if ($result && count($affectedKeys) > 0) {
68
            $this->deleteUnaffected($affectedKeys);
69
        }
70
71
        return $result;
72
    }
73
74
    /**
75
     * Returns the children key name to use on the mapping array
76
     *
77
     * @return string
78
     */
79
    public function getChildrenKeyName(): string
80
    {
81
        return $this->childrenKeyName;
82
    }
83
84
    /**
85
     * Maps a tree structure into the database
86
     *
87
     * @param   array   $tree
88
     * @param   mixed   $parent
89
     * @return  boolean
90
     */
91
    protected function mapTreeRecursive(array $tree, $parentKey = null, &$affectedKeys = []): bool
92
    {
93
        // For every attribute entry: We'll need to instantiate a new node either
94
        // from the database (if the primary key was supplied) or a new instance. Then,
95
        // append all the remaining data attributes (including the `parent_id` if
96
        // present) and save it. Finally, tail-recurse performing the same
97
        // operations for any child node present. Setting the `parent_id` property at
98
        // each level will take care of the nesting work for us.
99
        foreach ($tree as $attributes) {
100
            $node = $this->firstOrNew($this->getSearchAttributes($attributes));
101
102
            $data = $this->getDataAttributes($attributes);
103
            if (!is_null($parentKey)) {
104
                $data[$node->getParentColumnName()] = $parentKey;
105
            }
106
107
            $node->fill($data);
108
109
            $result = $node->save();
110
111
            if (!$result) {
112
                return false;
113
            }
114
115
            $affectedKeys[] = $node->getKey();
116
117
            if (array_key_exists($this->getChildrenKeyName(), $attributes)) {
118
                $children = $attributes[$this->getChildrenKeyName()];
119
120
                if (count($children) > 0) {
121
                    $result = $this->mapTreeRecursive($children, $node->getKey(), $affectedKeys);
122
123
                    if (!$result) {
124
                        return false;
125
                    }
126
                }
127
            }
128
        }
129
130
        return true;
131
    }
132
133
    protected function getSearchAttributes(array $attributes): array
134
    {
135
        $searchable = [$this->node->getKeyName()];
136
137
        return Arr::only($attributes, $searchable);
138
    }
139
140
    protected function getDataAttributes(array $attributes): array
141
    {
142
        $exceptions = [$this->node->getKeyName(), $this->getChildrenKeyName()];
143
144
        return Arr::except($attributes, $exceptions);
145
    }
146
147
    protected function firstOrNew(array $attributes): Model
148
    {
149
        $className = get_class($this->node);
150
151
        if (count($attributes) === 0) {
152
            return new $className();
153
        }
154
155
        return forward_static_call([$className, 'firstOrNew'], $attributes);
156
    }
157
158
    protected function pruneScope(): Builder
159
    {
160
        if ($this->node->exists) {
161
            return $this->node->descendants();
162
        }
163
164
        return $this->node->newNestedSetQuery();
165
    }
166
167
    protected function deleteUnaffected(array $keys = []): bool
168
    {
169
        return $this->pruneScope()->whereNotIn($this->node->getKeyName(), $keys)->delete();
170
    }
171
172
    protected function wrapInTransaction(Closure $callback)
173
    {
174
        return $this->node->getConnection()->transaction($callback);
175
    }
176
}
177