Completed
Push — master ( e84808...5a9d2e )
by Oleg
07:58
created

Builder::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 17
ccs 9
cts 9
cp 1
rs 9.4285
cc 2
eloc 10
nc 2
nop 1
crap 2
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 23
    public function __construct(Container $container, AttributesContract $attributes, 
63
                                AttributesContract $activeAttributes, 
64
                                $type = self::UL, $view = null)
65
    {
66 23
        $this->app = $container;
67 23
        $this->type = $type;
68 23
        $this->attributes = $attributes;
69 23
        $this->elements = [];
70 23
        $this->activeAttributes = $activeAttributes;
71 23
        $this->viewFactory = $this->app->make(MenuRender::class);
72 23
        $this->config = $this->app->make(Repository::class)->get('menu');
73
        try {
74 23
            $this->setView($view);
75 23
        } 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 23
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 19
    public function create($name, $type, $callback = null)
82
    {
83 19
        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 1
        $this->insert($this->indexes[$name], $this->prepareInsert($name, $callback));
118 1
    }
119
120
    /**
121
     * @inheritDoc
122
     */
123
    public function insertAfter($name, \Closure $callback)
124
    {
125 1
        $this->insert($this->indexes[$name] + 1, $this->prepareInsert($name, $callback));
126 1
    }
127
128
    /**
129
     * @inheritDoc
130
     */
131
    public function has($name)
132
    {
133 20
        return array_key_exists($name, $this->elements);
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139
    public function get($name, $default = null)
140
    {
141 3
        if ($this->has($name)) {
142 3
            return $this->elements[$name];
143
        }
144 1
        return $default;
145
    }
146
147
    /**
148
     * @inheritDoc
149
     */
150
    public function getByIndex($index, $default = null)
151
    {
152 1
        $key = array_search($index, $this->indexes);
153
        
154 1
        return $key === false ? $default : $this->get($key, $default);
155
    }
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function all()
161
    {
162 8
        return $this->elements;
163
    }
164
165
    /**
166
     * @inheritDoc
167
     */
168
    public function forget($name)
169
    {
170 1
        if ($this->has($name)) {
171 1
            unset($this->elements[$name]);
172
        }
173 1
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function getType()
179
    {
180 8
        return $this->type;
181
    }
182
183
    /**
184
     * @inheritDoc
185
     */
186
    public function setType($type)
187
    {
188 1
        $this->type = (string) $type;
189 1
    }
190
191
    /**
192
     * @inheritDoc
193
     */
194
    public function render($renderView = null)
195
    {
196 7
        $view = $this->getRenderView($renderView);
197
198 7
        $minify = $this->config['minify'];
199
200 7
        $rendered = $this->viewFactory->make($view)->with([
201 7
            'menu' => $this,
202 7
            'renderView' => $renderView,
203 7
        ])->render();
204
        
205 7
        if ($minify) {
206 7
            $rendered = $this->minifyHtmlOutput($rendered);
207
        }
208
        
209 7
        return $rendered;
210
    }
211
212
    /**
213
     * @inheritDoc
214
     */
215
    public function getView()
216
    {
217 1
        return $this->view;
218
    }
219
220
    /**
221
     * @inheritDoc
222
     */
223
    public function setView($view)
224
    {
225 23
        if (!$this->viewFactory->exists($view)) {
226 23
            throw new \Exception('View not found');
227
        }
228
        
229 3
        $this->view = $view;
230 3
    }
231
232
    /**
233
     * Minify html
234
     *
235
     * @param string $html
236
     * @return string
237
     */
238
    protected function minifyHtmlOutput($html)
239
    {
240
        $search = array(
241 7
            '/\>[^\S]+/s', // strip whitespaces after tags, except space
242
            '/[^\S]+\</s', // strip whitespaces before tags, except space
243
            '/(\s)+/s'       // shorten multiple whitespace sequences
244
        );
245
246
        $replace = array(
247 7
            '>',
248
            '<',
249
            '\\1'
250
        );
251
252 7
        return preg_replace($search, $replace, $html);
253
    }
254
255
    /**
256
     * Get view for render
257
     * 
258
     * @param string $view
259
     * @return string
260
     */
261
    protected function getRenderView($view = null)
262
    {
263 9
        $renderView = $this->config['view'];
264
        
265 9
        if (!empty($this->view)) {
266 2
            $renderView = $this->view;
267
        }
268
        
269 9
        if (!empty($view) && $this->viewFactory->exists($view)) {
270 2
            $renderView = $view;
271
        }
272
        
273 9
        return $renderView;
274
    }
275
276
    /**
277
     * @param string $name
278
     * @param Element $item
279
     */
280
    protected function saveItem($name, $item)
281
    {
282 19
        $this->elements[$name] = $item;
283 19
        $this->indexes[$name] = count($this->elements) - 1;
284 19
    }
285
286
    /**
287
     * @param string $name
288
     * @param array $attributes
289
     * @param array $activeAttributes
290
     * @param \Closure|null $callback
291
     * @return BuilderContract
292
     */
293
    protected function builderFactory($name, $attributes = [], $activeAttributes = [], $callback = null)
294
    {
295 1
        $menu = $this->app->make(BuilderContract::class, [
296 1
            'container' => $this->app,
297 1
            'name' => $name,
298 1
            'activeAttributes' => $this->app->make(AttributesContract::class, ['attributes' => $activeAttributes]),
299 1
            'attributes' => $this->app->make(AttributesContract::class, ['attributes' => $attributes]),
300 1
            'view' => $this->getView(),
301
        ]);
302
303 1
        if (is_callable($callback)) {
304 1
            call_user_func($callback, $menu);
305
        }
306
307 1
        return $menu;
308
    }
309
310
    protected function rebuildIndexesArray()
311
    {
312 1
        $this->indexes = [];
313 1
        $iterator = 0;
314
315 1
        foreach ($this->elements as $key => $value) {
316 1
            $this->indexes[$key] = $iterator++;
317
        }
318 1
    }
319
320
    /**
321
     * @param string $name
322
     * @param \Closure $callback
323
     * @return array
324
     */
325
    protected function prepareInsert($name, $callback)
326
    {
327 1
        if (!$this->has($name)) {
328
            throw new \RuntimeException("Menu item \"${name}\" must be exists");
329
        }
330
331 1
        $forInsert = $this->builderFactory('tmp', [], [], $callback)->all();
332 1
        $diff = array_diff(array_keys(array_diff_key($this->elements, $forInsert)), array_keys($this->elements));
333
334 1
        if (count($diff) > 0) {
335
            throw new \RuntimeException('Duplicated keys: ' . implode(', ', array_keys($diff)));
336
        }
337
        
338 1
        return $forInsert;
339
    }
340
341
    /**
342
     * @param int $position
343
     * @param array $values
344
     */
345
    protected function insert($position, $values)
346
    {
347 1
        $firstArray = array_splice($this->elements, 0, $position);
348 1
        $this->elements = array_merge($firstArray, $values, $this->elements);
349 1
        $this->rebuildIndexesArray();
350 1
    }
351
352
    /**
353
     * @param $element
354
     * @return ElementFactory
355
     */
356
    protected function getFactory($element)
357
    {
358 19
        $factoryClass = $this->app->make(Repository::class)->get('menu.elements')[$element]['factory'];
359
        
360 19
        return $this->app->make($factoryClass);
361
    }
362
363
    /**
364
     * @inheritDoc
365
     */
366
    public function toArray()
367
    {
368 2
        $this->view = $this->getRenderView($this->view);
369 2
        $elements = [];
370
        
371 2
        foreach ($this->elements as $key => $element) {
372 2
            $elements[$key] = $element->toArray();
373 2
            $elements[$key]['type'] = array_search(get_class($element), $this->config['alias']);
374
        }
375
        
376
        return [
377 2
            'type' => $this->type,
378 2
            'view' => $this->view,
379 2
            'attributes' => $this->attributes->toArray(),
380 2
            'activeAttributes' => $this->activeAttributes->toArray(),
381 2
            'elements' => $elements,
382
        ];
383
    }
384
385
    /**
386
     * @inheritDoc
387
     */
388
    public function offsetExists($offset)
389
    {
390
        return $this->has($offset);
391
    }
392
393
    /**
394
     * @inheritDoc
395
     */
396
    public function offsetGet($offset)
397
    {
398
        return $this->get($offset);
399
    }
400
401
    /**
402
     * @inheritDoc
403
     */
404
    public function offsetSet($offset, $value)
405
    {
406
        $this->elements[$offset] = $value;
407
    }
408
409
    /**
410
     * @inheritDoc
411
     */
412
    public function offsetUnset($offset)
413
    {
414
        $this->forget($offset);
415
    }
416
417
    /**
418
     * @inheritDoc
419
     */
420
    public function serialize()
421
    {
422 1
        return serialize([
423 1
            'type' => $this->type,
424 1
            'view' => $this->view,
425 1
            'attributes' => $this->attributes,
426 1
            'activeAttributes' => $this->activeAttributes,
427 1
            'elements' => $this->elements,
428
        ]);
429
    }
430
431
    /**
432
     * @inheritDoc
433
     */
434
    public function unserialize($serialized)
435
    {
436
        $this->app = \Illuminate\Container\Container::getInstance();
437
        $this->viewFactory = $this->app->make(MenuRender::class);
438
        $this->config = $this->app->make(Repository::class)->get('menu');
439
440
        $data = unserialize($serialized);
441
442
        foreach ($data as $key => $value) {
443
            if (property_exists($this, $key)) {
444
                $this->{$key} = $value;
445
            }
446
        }
447
        
448
        $this->rebuildIndexesArray();
449
    }
450
451
    /**
452
     * @inheritDoc
453
     */
454
    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...
455
    {
456 2
        $app = \Illuminate\Container\Container::getInstance();
457 2
        $config = $app->make(Repository::class)->get('menu');
458
        
459
        /** @var self $builderObject */
460 2
        $builderObject = $app->make(self::class, [
461 2
            'attributes' => $app->make(AttributesContract::class, ['attributes' => $builder['attributes']]),
462 2
            'activeAttributes' => $app->make(AttributesContract::class, ['attributes' => $builder['activeAttributes']]),
463 2
            'view' => $builder['view'],
464 2
            'type' => $builder['type'],
465
        ]);
466
        
467 2
        foreach ($builder['elements'] as $key => $element) {
468 2
            $class = $config['alias'][$element['type']];
469
            
470
            $builderObject->create($key, $class, function(ElementFactory $factory) use ($class, $element, $app) {
471 2
                $reflection = new \ReflectionClass($class);
472 2
                if ($reflection->implementsInterface(HasBuilder::class)) {
473 2
                    $element['builder'] = self::fromArray($element['builder']);
474
                }
475
476 2
                $attributes = preg_grep("/.*(attributes)/i", array_keys($element));
477
478 2
                foreach ($attributes as $key) {
479 2
                    $element[$key] = $app->make(AttributesContract::class, ['attributes' => $element[$key]]);
480
                }
481
                
482 2
                return $factory->build($element);
483 2
            });
484
        }
485
        
486 2
        return $builderObject;
487
    }
488
}