Passed
Push — master ( af3dcf...3709c1 )
by Petr
02:16
created

MetaProcessor   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 107
c 0
b 0
f 0
dl 0
loc 264
ccs 129
cts 129
cp 1
rs 7.44
wmc 52

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getMenu() 0 3 1
A load() 0 6 4
A clearData() 0 6 1
A __construct() 0 6 1
A save() 0 12 2
A maxPosition() 0 3 1
A setKey() 0 4 1
A updateInfo() 0 7 4
A getWorking() 0 4 1
A addEntry() 0 7 3
A sortItems() 0 3 1
A removeEntry() 0 4 2
A menuPosition() 0 3 1
C rearrangePositions() 0 43 12
A clearHoles() 0 28 6
A sortWorkList() 0 3 1
A exists() 0 3 1
A getEntry() 0 8 3
A updateEntry() 0 14 5
A getHighestKnownPosition() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like MetaProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetaProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace kalanis\kw_menu;
4
5
6
use kalanis\kw_menu\Interfaces\IMNTranslations;
7
use kalanis\kw_menu\Traits\TLang;
8
9
10
/**
11
 * Class MetaProcessor
12
 * @package kalanis\kw_menu
13
 * Menu data processor - CRUD
14
 */
15
class MetaProcessor
16
{
17
    use TLang;
18
19
    /** @var Interfaces\IMetaSource|null */
20
    protected $metaSource = null;
21
    /** @var Menu\Menu */
22
    protected $menu = null;
23
    /** @var Menu\Entry */
24
    protected $entry = null;
25
    /** @var int */
26
    protected $highest = 0;
27
    /** @var Menu\Entry[] */
28
    protected $workList = [];
29
30 8
    public function __construct(Interfaces\IMetaSource $metaSource, ?IMNTranslations $lang = null)
31
    {
32 8
        $this->menu = new Menu\Menu();
33 8
        $this->entry = new Menu\Entry();
34 8
        $this->metaSource = $metaSource;
35 8
        $this->setMnLang($lang);
36 8
    }
37
38
    /**
39
     * @param string[] $metaSource
40
     * @return $this
41
     * @throws MenuException
42
     */
43 8
    public function setKey(array $metaSource): self
44
    {
45 8
        $this->metaSource->setSource($metaSource);
0 ignored issues
show
Bug introduced by
The method setSource() does not exist on null. ( Ignorable by Annotation )

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

45
        $this->metaSource->/** @scrutinizer ignore-call */ 
46
                           setSource($metaSource);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
46 8
        return $this;
47
    }
48
49
    /**
50
     * @return bool
51
     * @throws MenuException
52
     */
53 8
    public function exists(): bool
54
    {
55 8
        return $this->metaSource->exists();
56
    }
57
58
    /**
59
     * @return Menu\Menu
60
     */
61 1
    public function getMenu(): Menu\Menu
62
    {
63 1
        return $this->menu;
64
    }
65
66
    /**
67
     * @throws MenuException
68
     */
69 6
    public function load(): void
70
    {
71 6
        if (empty($this->menu->getEntries()) && empty($this->menu->getFile()) && $this->exists()) {
72 6
            $this->menu = $this->metaSource->load();
73 6
            $this->workList = $this->menu->getEntries();
74 6
            $this->highest = max([0] + array_map([$this, 'menuPosition'], $this->workList));
75
        }
76 6
    }
77
78 6
    public function menuPosition(Menu\Entry $item): int
79
    {
80 6
        return $item->getPosition();
81
    }
82
83 1
    public function updateInfo(?string $name, ?string $desc, ?int $displayCount): void
84
    {
85 1
        $this->menu->setData(
86 1
            $this->menu->getFile(),
87 1
            $name ?: $this->menu->getName(),
88 1
            $desc ?: $this->menu->getTitle(),
89 1
            $displayCount ?: $this->highest
90
        );
91 1
    }
92
93 4
    public function getEntry(string $id): ?Menu\Entry
94
    {
95 4
        foreach ($this->workList as &$entry) {
96 4
            if ($entry->getId() == $id) {
97 2
                return $entry;
98
            }
99
        }
100 4
        return null;
101
    }
102
103
    /**
104
     * @return Menu\Entry[]
105
     */
106 2
    public function getWorking(): array
107
    {
108 2
        $this->sortItems();
109 2
        return $this->workList;
110
    }
111
112 3
    public function addEntry(string $id, string $name = '', string $desc = '', bool $sub = false): void
113
    {
114 3
        if (!$this->getEntry($id)) {
115 3
            $name = empty($name) ? $id : $name;
116 3
            $item = clone $this->entry;
117 3
            $this->highest++;
118 3
            $this->workList[$id] = $item->setData($id, $name, $desc, $this->highest, $sub);
119
        }
120 3
    }
121
122
    /**
123
     * @param string $id
124
     * @param string|null $name
125
     * @param string|null $desc
126
     * @param bool|null $sub
127
     * @throws MenuException
128
     */
129 2
    public function updateEntry(string $id, ?string $name, ?string $desc, ?bool $sub): void
130
    {
131
        # null sign means not free, just unchanged
132 2
        $item = $this->getEntry($id);
133 2
        if (!$item) {
134 1
            throw new MenuException($this->getMnLang()->mnItemNotFound($id));
135
        }
136
137 1
        $item->setData(
138 1
            $id,
139 1
            is_null($name) ? $item->getName() : $name,
140 1
            is_null($desc) ? $item->getDesc() : $desc,
141 1
            $item->getPosition(),
142 1
            is_null($sub) ? $item->canGoSub() : $sub
143
        );
144 1
    }
145
146 2
    public function removeEntry(string $id): void
147
    {
148 2
        if ($item = $this->getEntry($id)) {
149 2
            unset($this->workList[$item->getId()]);
150
        }
151 2
    }
152
153
    /**
154
     * get assoc array with new positioning of files
155
     * key is file name, value is new position
156
     * @param array<string, int> $positions
157
     * @throws MenuException
158
     */
159 4
    public function rearrangePositions(array $positions): void
160
    {
161 4
        if (empty($positions)) {
162 1
            throw new MenuException($this->getMnLang()->mnProblematicData());
163
        }
164 3
        $matrix = [];
165
        # all at first
166 3
        foreach ($this->workList as &$item) {
167 2
            $matrix[$item->getPosition()] = $item->getPosition();
168
        }
169
        # updated at second
170 3
        foreach ($positions as $id => &$position) {
171 3
            if (empty($this->workList[$id])) {
172 1
                throw new MenuException($this->getMnLang()->mnItemNotFound($id));
173
            }
174 2
            if (!is_numeric($position)) {
175 1
                throw new MenuException($this->getMnLang()->mnProblematicData());
176
            }
177 1
            $matrix[$this->workList[$id]->getPosition()] = intval($position);
178
        }
179
180 1
        $prepared = [];
181 1
        foreach ($matrix as $old => &$new) {
182 1
            if ($old == $new) { # don't move, stay there
183 1
                $prepared[$new] = $old;
184 1
                unset($matrix[$old]);
185
            }
186
        }
187
188 1
        while (count($matrix) > 0) {
189 1
            foreach ($matrix as $old => &$new) {
190 1
                if (!isset($prepared[$new])) { # nothing on new position
191 1
                    $prepared[$new] = $old;
192 1
                    unset($matrix[$old]);
193
                } else {
194 1
                    $matrix[$old]++; # on next round try next position
195
                }
196
            }
197
        }
198
199 1
        $prepared = array_flip($prepared); # flip positions back, index is original one, not new one
200 1
        foreach ($this->workList as &$item) {
201 1
            $item->setPosition($prepared[$item->getPosition()]);
202
        }
203 1
    }
204
205 2
    public function clearData(): self
206
    {
207 2
        $this->highest = $this->getHighestKnownPosition();
208 2
        $this->clearHoles();
209 2
        $this->highest = $this->getHighestKnownPosition();
210 2
        return $this;
211
    }
212
213 2
    protected function clearHoles(): void
214
    {
215 2
        $max = $this->highest;
216 2
        $use = [];
217 2
        $hole = false;
218 2
        $j = 0;
219
220
        /** @var Menu\Entry[] $workList */
221 2
        $workList = [];
222 2
        foreach ($this->workList as &$item) {
223 2
            $workList[$item->getPosition()] = $item->getId(); # old position contains file ***
224
        }
225
226 2
        for ($i = 0; $i <= $max; $i++) {
227 2
            if (!empty($workList[$i])) { # position contains data
228 2
                $use[$j] = $workList[$i]; # new position contains file named *** from old one...
229 2
                $j++;
230 2
                $hole = false;
231 2
            } elseif (!$hole) { # first free position
232 2
                $j++;
233 2
                $hole = true;
234
            }
235
            # more than one free position
236
        }
237 2
        $use = array_flip($use); # flip back to names as PK
238
239 2
        foreach ($this->workList as &$item) {
240 2
            $item->setPosition($use[$item->getId()]);
241
        }
242 2
    }
243
244 2
    protected function getHighestKnownPosition(): int
245
    {
246 2
        return intval(array_reduce($this->workList, [$this, 'maxPosition'], 0));
247
    }
248
249 2
    public function maxPosition(int $carry, Menu\Entry $item): int
250
    {
251 2
        return intval(max($carry, $item->getPosition()));
252
    }
253
254
    /**
255
     * @throws MenuException
256
     */
257 2
    public function save(): void
258
    {
259 2
        $this->menu->setData( // reset entries from working list
260 2
            $this->menu->getFile(),
261 2
            $this->menu->getName(),
262 2
            $this->menu->getTitle(),
263 2
            $this->menu->getDisplayCount()
264
        );
265 2
        foreach ($this->workList as &$item) {
266 2
            $this->menu->addItem($item);
267
        }
268 2
        $this->metaSource->save($this->menu);
269 2
    }
270
271 2
    protected function sortItems(): void
272
    {
273 2
        uasort($this->workList, [$this, 'sortWorkList']);
274 2
    }
275
276 2
    public function sortWorkList(Menu\Entry $a, Menu\Entry $b): int
277
    {
278 2
        return $a->getPosition() <=> $b->getPosition();
279
    }
280
}
281