Completed
Push — master ( 5a9d2e...4895be )
by Oleg
04:31
created

Builder::all()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
namespace Malezha\Menu;
3
4
use Illuminate\Contracts\Config\Repository;
5
use Illuminate\Contracts\Container\Container;
6
use Malezha\Menu\Contracts\Attributes as AttributesContract;
7
use Malezha\Menu\Contracts\Builder as BuilderContract;
8
use Malezha\Menu\Contracts\Element;
9
use Malezha\Menu\Contracts\ElementFactory;
10
use Malezha\Menu\Contracts\HasActiveAttributes;
11
use Malezha\Menu\Contracts\HasBuilder;
12
use Malezha\Menu\Contracts\MenuRender;
13
use Malezha\Menu\Traits\HasActiveAttributes as TraitHasActiveAttributes;
14
use Malezha\Menu\Traits\HasAttributes;
15
16
/**
17
 * Class Builder
18
 * @package Malezha\Menu
19
 */
20
class Builder implements BuilderContract
21
{
22
    use HasAttributes, TraitHasActiveAttributes;
23
24
    /**
25
     * @var Container
26
     */
27
    protected $app;
28
29
    /**
30
     * @var array
31
     */
32
    protected $elements;
33
34
    /**
35
     * @var array
36
     */
37
    protected $indexes = [];
38
39
    /**
40
     * @var string
41
     */
42
    protected $type;
43
44
    /**
45
     * @var MenuRender
46
     */
47
    protected $viewFactory;
48
49
    /**
50
     * @var array
51
     */
52
    protected $config;
53
54
    /**
55
     * @var string
56
     */
57
    protected $view = null;
58
    
59
    /**
60
     * @inheritDoc
61
     */
62 26
    public function __construct(Container $container, AttributesContract $attributes, 
63
                                AttributesContract $activeAttributes, 
64
                                $type = self::UL, $view = null)
65
    {
66 26
        $this->app = $container;
67 26
        $this->type = $type;
68 26
        $this->attributes = $attributes;
69 26
        $this->elements = [];
70 26
        $this->activeAttributes = $activeAttributes;
71 26
        $this->viewFactory = $this->app->make(MenuRender::class);
72 26
        $this->config = $this->app->make(Repository::class)->get('menu');
73
        try {
74 26
            $this->setView($view);
75 26
        } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
76 26
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 20
    public function create($name, $type, $callback = null)
82
    {
83 20
        if ($this->has($name)) {
84 1
            throw new \RuntimeException("Duplicate menu key \"${name}\"");
85
        }
86
87
        $factory = $this->getFactory($type);
88
        $result = null;
89
90
        $reflection = new \ReflectionClass($type);
91
        if ($reflection->implementsInterface(HasActiveAttributes::class)) {
92
            $factory->activeAttributes = clone $this->activeAttributes;
0 ignored issues
show
Bug introduced by
Accessing activeAttributes on the interface Malezha\Menu\Contracts\ElementFactory suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
93
        }
94
        
95
        if (is_callable($callback)) {
96
            $result = call_user_func($callback, $factory);
97
            
98
            if (empty($result)) {
99
                $result = $factory;
100
            }
101
        }
102
        
103
        if ($result instanceof ElementFactory) {
104
            $result = $result->build();
105
        }
106
        
107
        $this->saveItem($name, $result);
108
        
109
        return $result;
110
    }
111
112
    /**
113
     * @inheritDoc
114
     */
115
    public function insertBefore($name, \Closure $callback)
116
    {
117 2
        $prepared = $this->prepareInsert($name, $callback);
118 1
        $this->insert($this->indexes[$name], $prepared);
119 1
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124
    public function insertAfter($name, \Closure $callback)
125
    {
126 2
        $prepared = $this->prepareInsert($name, $callback);
127 1
        $this->insert($this->indexes[$name] + 1, $prepared);
128 1
    }
129
130
    /**
131
     * @inheritDoc
132
     */
133
    public function has($name)
134
    {
135 23
        return array_key_exists($name, $this->elements);
136
    }
137
138
    /**
139
     * @inheritDoc
140
     */
141
    public function get($name, $default = null)
142
    {
143 3
        if ($this->has($name)) {
144 3
            return $this->elements[$name];
145
        }
146 1
        return $default;
147
    }
148
149
    /**
150
     * @inheritDoc
151
     */
152
    public function getByIndex($index, $default = null)
153
    {
154 1
        $key = array_search($index, $this->indexes);
155
        
156 1
        return $key === false ? $default : $this->get($key, $default);
157
    }
158
159
    /**
160
     * @inheritDoc
161
     */
162
    public function all()
163
    {
164 9
        return $this->elements;
165
    }
166
167
    /**
168
     * @inheritDoc
169
     */
170
    public function forget($name)
171
    {
172 1
        if ($this->has($name)) {
173 1
            unset($this->elements[$name]);
174 1
        }
175 1
    }
176
177
    /**
178
     * @inheritDoc
179
     */
180
    public function getType()
181
    {
182 8
        return $this->type;
183
    }
184
185
    /**
186
     * @inheritDoc
187
     */
188
    public function setType($type)
189
    {
190 1
        $this->type = (string) $type;
191 1
    }
192
193
    /**
194
     * @inheritDoc
195
     */
196
    public function render($renderView = null)
197
    {
198 7
        $view = $this->getRenderView($renderView);
199
200 7
        $minify = $this->config['minify'];
201
202 7
        $rendered = $this->viewFactory->make($view)->with([
203 7
            'menu' => $this,
204 7
            'renderView' => $renderView,
205 7
        ])->render();
206
        
207 7
        if ($minify) {
208 7
            $rendered = $this->minifyHtmlOutput($rendered);
209 7
        }
210
        
211 7
        return $rendered;
212
    }
213
214
    /**
215
     * @inheritDoc
216
     */
217
    public function getView()
218
    {
219 2
        return $this->view;
220
    }
221
222
    /**
223
     * @inheritDoc
224
     */
225
    public function setView($view)
226
    {
227 26
        if (!$this->viewFactory->exists($view)) {
228 26
            throw new \Exception('View not found');
229
        }
230
        
231 3
        $this->view = $view;
232 3
    }
233
234
    /**
235
     * Minify html
236
     *
237
     * @param string $html
238
     * @return string
239
     */
240
    protected function minifyHtmlOutput($html)
241
    {
242
        $search = array(
243 7
            '/\>[^\S]+/s', // strip whitespaces after tags, except space
244 7
            '/[^\S]+\</s', // strip whitespaces before tags, except space
245 1
            '/(\s)+/s'       // shorten multiple whitespace sequences
246 7
        );
247
248 1
        $replace = array(
249 7
            '>',
250 7
            '<',
251
            '\\1'
252 7
        );
253
254 7
        return preg_replace($search, $replace, $html);
255 1
    }
256
257
    /**
258
     * Get view for render
259
     * 
260
     * @param string $view
261
     * @return string
262
     */
263
    protected function getRenderView($view = null)
264
    {
265 9
        $renderView = $this->config['view'];
266
        
267 9
        if (!empty($this->view)) {
268 3
            $renderView = $this->view;
269 3
        }
270
        
271 9
        if (!empty($view) && $this->viewFactory->exists($view)) {
272 3
            $renderView = $view;
273 3
        }
274
        
275 9
        return $renderView;
276
    }
277
278
    /**
279
     * @param string $name
280
     * @param Element $item
281
     */
282
    protected function saveItem($name, $item)
283
    {
284 20
        $this->elements[$name] = $item;
285 20
        $this->indexes[$name] = count($this->elements) - 1;
286 20
    }
287
288
    /**
289
     * @param string $name
290
     * @param array $attributes
291
     * @param array $activeAttributes
292
     * @param \Closure|null $callback
293
     * @return BuilderContract
294
     */
295
    protected function builderFactory($name, $attributes = [], $activeAttributes = [], $callback = null)
296
    {
297 2
        $menu = $this->app->make(BuilderContract::class, [
298 2
            'container' => $this->app,
299 2
            'name' => $name,
300 2
            'activeAttributes' => $this->app->make(AttributesContract::class, ['attributes' => $activeAttributes]),
301 2
            'attributes' => $this->app->make(AttributesContract::class, ['attributes' => $attributes]),
302 2
            'view' => $this->getView(),
303 2
        ]);
304
305 2
        if (is_callable($callback)) {
306 2
            call_user_func($callback, $menu);
307 3
        }
308
309 2
        return $menu;
310
    }
311
312
    protected function rebuildIndexesArray()
313 1
    {
314 2
        $this->indexes = [];
315 2
        $iterator = 0;
316
317 2
        foreach ($this->elements as $key => $value) {
318 2
            $this->indexes[$key] = $iterator++;
319 2
        }
320 2
    }
321
322
    /**
323
     * @param string $name
324
     * @param \Closure $callback
325
     * @return array
326
     */
327
    protected function prepareInsert($name, $callback)
328
    {
329 3
        if (!$this->has($name)) {
330 1
            throw new \RuntimeException("Menu item \"${name}\" must be exists");
331 1
        }
332
333 2
        $forInsert = $this->builderFactory('tmp', [], [], $callback)->all();
334 2
        $diff = array_diff(array_keys(array_diff_key($this->elements, $forInsert)), array_keys($this->elements));
0 ignored issues
show
Unused Code introduced by
$diff is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
335 2
        $diff = array_intersect_key($this->elements, $forInsert);
336
337 2
        if (count($diff) > 0) {
338 1
            throw new \RuntimeException('Duplicated keys: ' . implode(', ', array_keys($diff)));
339
        }
340
        
341 1
        return $forInsert;
342 1
    }
343
344
    /**
345
     * @param int $position
346
     * @param array $values
347
     */
348
    protected function insert($position, $values)
349 1
    {
350 1
        $firstArray = array_splice($this->elements, 0, $position);
351 1
        $this->elements = array_merge($firstArray, $values, $this->elements);
352 1
        $this->rebuildIndexesArray();
353 1
    }
354
355
    /**
356
     * @param $element
357
     * @return ElementFactory
358
     */
359
    protected function getFactory($element)
360
    {
361 20
        $factoryClass = $this->app->make(Repository::class)->get('menu.elements')[$element]['factory'];
362
        
363 20
        return $this->app->make($factoryClass);
364
    }
365
366
    /**
367
     * @inheritDoc
368
     */
369
    public function toArray()
370 1
    {
371 3
        $this->view = $this->getRenderView($this->view);
372 2
        $elements = [];
373
        
374 3
        foreach ($this->elements as $key => $element) {
375 3
            $elements[$key] = $element->toArray();
376 2
            $elements[$key]['type'] = array_search(get_class($element), $this->config['alias']);
377 2
        }
378
        
379 1
        return [
380 2
            'type' => $this->type,
381 2
            'view' => $this->view,
382 2
            'attributes' => $this->attributes->toArray(),
383 2
            'activeAttributes' => $this->activeAttributes->toArray(),
384 2
            'elements' => $elements,
385 2
        ];
386
    }
387
388
    /**
389
     * @inheritDoc
390
     */
391
    public function offsetExists($offset)
392 1
    {
393 2
        if (is_int($offset)) {
394 1
            return (bool) array_search($offset, $this->indexes, true);
395 1
        }
396 2
        return $this->has($offset);
397
    }
398
399
    /**
400
     * @inheritDoc
401
     */
402
    public function offsetGet($offset)
403 1
    {
404 3
        if (is_int($offset)) {
405 2
            $offset = array_search($offset, $this->indexes, true);
406 2
            if ($offset === false) {
407 2
                return null;
408
            }
409 2
        }
410 3
        return $this->get($offset);
411 1
    }
412
413
    /**
414
     * @inheritDoc
415
     */
416
    public function offsetSet($offset, $value)
417 1
    {
418 2
        if ($value instanceof Element) {
419 1
            $this->elements[$offset] = $value;
420 1
        }
421 2
    }
422
423
    /**
424
     * @inheritDoc
425
     */
426
    public function offsetUnset($offset)
427
    {
428 1
        $this->forget($offset);
429 1
    }
430
431
    /**
432
     * @inheritDoc
433
     */
434
    public function serialize()
435
    {
436 2
        return serialize([
437 2
            'type' => $this->type,
438 1
            'view' => $this->view,
439 1
            'attributes' => $this->attributes,
440 1
            'activeAttributes' => $this->activeAttributes,
441 2
            'elements' => $this->elements,
442 2
        ]);
443 1
    }
444
445
    /**
446
     * @inheritDoc
447
     */
448
    public function unserialize($serialized)
449
    {
450 1
        $this->app = \Illuminate\Container\Container::getInstance();
451 1
        $this->viewFactory = $this->app->make(MenuRender::class);
452 1
        $this->config = $this->app->make(Repository::class)->get('menu');
453
454 1
        $data = unserialize($serialized);
455
456 1
        foreach ($data as $key => $value) {
457 1
            if (property_exists($this, $key)) {
458 1
                $this->{$key} = $value;
459 1
            }
460 1
        }
461
        
462 1
        $this->rebuildIndexesArray();
463 1
    }
464
465
    /**
466
     * @inheritDoc
467
     */
468
    static public function fromArray(array $builder)
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
469
    {
470 2
        $app = \Illuminate\Container\Container::getInstance();
471 2
        $config = $app->make(Repository::class)->get('menu');
472
        
473
        /** @var self $builderObject */
474 2
        $builderObject = $app->make(self::class, [
475 2
            'attributes' => $app->make(AttributesContract::class, ['attributes' => $builder['attributes']]),
476 2
            'activeAttributes' => $app->make(AttributesContract::class, ['attributes' => $builder['activeAttributes']]),
477 2
            'view' => $builder['view'],
478 2
            'type' => $builder['type'],
479 2
        ]);
480
        
481 2
        foreach ($builder['elements'] as $key => $element) {
482 2
            $class = $config['alias'][$element['type']];
483
            
484
            $builderObject->create($key, $class, function(ElementFactory $factory) use ($class, $element, $app) {
485 2
                $reflection = new \ReflectionClass($class);
486 2
                if ($reflection->implementsInterface(HasBuilder::class)) {
487 2
                    $element['builder'] = self::fromArray($element['builder']);
488 2
                }
489
490 2
                $attributes = preg_grep("/.*(attributes)/i", array_keys($element));
491
492 2
                foreach ($attributes as $key) {
493 2
                    $element[$key] = $app->make(AttributesContract::class, ['attributes' => $element[$key]]);
494 2
                }
495
                
496 2
                return $factory->build($element);
497 2
            });
498 2
        }
499
        
500 2
        return $builderObject;
501
    }
502
}