Completed
Push — master ( 1a0fcd...4e3f18 )
by Aleksandar
32s
created

MenuService::buildTree()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Menu\Service;
6
7
use Category\Service\CategoryService;
8
use Menu\Filter\MenuFilter;
9
use Menu\Mapper\MenuMapper;
10
use MysqlUuid\Formats\Binary;
11
use MysqlUuid\Uuid as MysqlUuid;
12
use Page\Service\PageService;
13
use Ramsey\Uuid\Uuid;
14
use Std\FilterException;
15
use Zend\Db\Sql\Expression;
16
17
class MenuService
18
{
19
    private $menuMapper;
20
    private $menuFilter;
21
    private $categoryService;
22
    private $pageService;
23
24
    public function __construct(
25
        MenuMapper $menuMapper,
26
        MenuFilter $menuFilter,
27
        CategoryService $categoryService,
28
        PageService $pageService
29
    ) {
30
        $this->menuMapper = $menuMapper;
31
        $this->menuFilter = $menuFilter;
32
        $this->categoryService = $categoryService;
33
        $this->pageService = $pageService;
34
    }
35
36
    /**
37
     * We store menu items in DB as flat structure,
38
     * but we need nested(tree) structure to show in the menu.
39
     *
40
     * @param array    $flatArray Array from DB
41
     * @param int|bool $parent
42
     *
43
     * @return array Return same array with tree structure
44
     */
45
    private function buildTree(array $flatArray, $parent = null)
46
    {
47
        $result = [];
48
49
        foreach ($flatArray as $element) {
50
            if ($element['parent_id'] == $parent) {
51
                $children = $this->buildTree($flatArray, $element['menu_id']);
52
                $element['children'] = ($children) ? $children : [];
53
                $result[] = $element;
54
            }
55
        }
56
57
        return $result;
58
    }
59
60
    public function getNestedAll($isActive = null, $filter = [])
61
    {
62
        $items = $this->menuMapper->selectAll($isActive, $filter)->toArray();
63
64
        return $this->buildTree($items);
65
    }
66
67
    public function get($id)
68
    {
69
        return $this->menuMapper->get($id);
70
    }
71
72
    public function addMenuItem($data)
73
    {
74
        $data  = $this->filterMenuItem($data);
75
        $order = $this->getMaxParentsOrderNumber();
76
77
        $data['menu_id'] = Uuid::uuid1()->toString();
78
        $data['menu_uuid'] = (new MysqlUuid($data['menu_id']))->toFormat(new Binary());
79
        $data['order_no'] = (!empty($order)) ? ($order + 1) : 1;
80
81
        return $this->menuMapper->insertMenuItem($data);
82
    }
83
84
    public function updateMenuItem($data, $id)
85
    {
86
        $data = $this->filterMenuItem($data);
87
88
        return $this->menuMapper->updateMenuItem($data, $id);
89
    }
90
91
    public function delete($id)
92
    {
93
        $menu     = $this->menuMapper->select(['menu_id' => $id]);
94
        $children = $this->menuMapper->select(['parent_id' => $id]);
95
96
        if ($children->count()) {
97
            $menu = $menu->current();
98
            $this->menuMapper->update([
99
                'order_no'  => new Expression('order_no + ' . ($children->count() - 1))], [
100
                'parent_id' => $menu->parent_id,
101
                'order_no'  => new Expression('order_no > ' . ($menu->order_no))
102
            ]);
103
104
            foreach ($children->toArray() as $child) {
105
                $child['parent_id'] = $menu->parent_id;
106
                $child['order_no'] += ($menu->order_no - 1);
107
                $this->menuMapper->update($child, [
108
                    'menu_id' => $child['menu_id']
109
                ]);
110
            }
111
        }
112
113
        return $this->menuMapper->delete(['menu_id' => $id]);
114
    }
115
116
    public function getForSelect()
117
    {
118
        return $this->menuMapper->forSelect();
119
    }
120
121
    public function updateMenuOrder($menuOrder)
122
    {
123
        if (!$menuOrder) {
124
            return true;
125
        }
126
127
        try {
128
            $this->menuMapper->getAdapter()->getDriver()->getConnection()->beginTransaction();
129
            $this->updateLevel($menuOrder, null);
130
            $this->menuMapper->getAdapter()->getDriver()->getConnection()->commit();
131
        } catch (\Exception $e) {
132
            $this->menuMapper->getAdapter()->getDriver()->getConnection()->rollback();
133
134
            throw $e;
135
        }
136
137
        return true;
138
    }
139
140
    private function updateLevel($children, $parentId = null)
141
    {
142
        $i=0; foreach ($children as $v) { $i++;
143
            if (isset($v->children)) {
144
                $this->menuMapper->update(['order_no' => $i, 'parent_id' => $parentId], ['menu_id' => $v->id]);
145
                $this->updateLevel($v->children, $v->id);
146
            } else {
147
                $this->menuMapper->update(['order_no' => $i, 'parent_id' => $parentId], ['menu_id' => $v->id]);
148
            }
149
        }
150
    }
151
152
    private function filterMenuItem($data)
153
    {
154
        $filter = $this->menuFilter->getInputFilter()->setData($data);
155
156
        if (!$filter->isValid()) {
157
            throw new FilterException($filter->getMessages());
158
        }
159
160
        if (count(array_filter([$data['page_id'], $data['category_id'], $data['href']])) > 1) {
161
            throw new \Exception('You need to set only one link. Post, Category or Href.');
162
        }
163
164
        $data = $filter->getValues();
165
166
        if ($data['page_id']) {
167
            $page = $this->pageService->getPage($data['page_id']);
168
            $data['page_uuid'] = $page->getPageUuid();
169
            $data['category_uuid'] = null;
170
        } elseif ($data['category_id']) {
171
            $category
172
                = $this->categoryService->getCategory($data['category_id']);
173
            $data['category_uuid'] = $category->category_uuid;
174
            $data['page_uuid'] = null;
175
        } else {
176
            $data['page_uuid'] = null;
177
            $data['category_uuid'] = null;
178
        }
179
180
        unset($data['page_id'], $data['category_id']);
181
182
        return $data;
183
    }
184
185
    /**
186
     * Returns max order number if found.
187
     *
188
     * @return integer|null
189
     */
190
    private function getMaxParentsOrderNumber()
191
    {
192
        $select = $this->menuMapper
193
            ->getSql()
194
            ->setTable('menu')
195
            ->select()
196
            ->where('parent_id IS NULL')
197
            ->columns(['order_no' => new Expression('MAX(order_no)')])
198
        ;
199
200
        $result = $this->menuMapper->selectWith($select)->current();
201
        return $result->order_no;
202
    }
203
}
204