Passed
Push — master ( c66270...550cde )
by Petr
07:42
created

MetaProcessor   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 110
dl 0
loc 268
ccs 134
cts 134
cp 1
rs 6.96
c 0
b 0
f 0
wmc 53

21 Methods

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