Flow   F
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 629
Duplicated Lines 0 %

Test Coverage

Coverage 90.52%

Importance

Changes 21
Bugs 0 Features 9
Metric Value
wmc 79
eloc 202
c 21
b 0
f 9
dl 0
loc 629
ccs 191
cts 211
cp 0.9052
rs 2.08

25 Methods

Rating   Name   Duplication   Size   Complexity  
A debugMode() 0 3 1
A ext() 0 3 1
A popDirective() 0 3 1
A lexeme() 0 8 2
A setLexer() 0 5 1
F buildWithoutCache() 0 104 28
A lexer() 0 8 2
A render() 0 5 1
A loadLexemes() 0 8 2
A pushDirective() 0 8 2
A setLexeme() 0 5 1
A directive() 0 10 2
A build() 0 7 1
A fragment() 0 10 1
A setNative() 0 10 3
A ifEnd() 0 21 4
A replace() 0 12 2
A fileSystem() 0 11 2
A minify() 0 19 3
A native() 0 8 2
A printers() 0 10 4
A operators() 0 28 5
A compile() 0 38 5
A __construct() 0 20 1
A path() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Flow 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 Flow, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bavix\Flow;
4
5
use Bavix\Exceptions\Invalid;
6
use Bavix\Exceptions\Runtime;
7
use Bavix\Flow\Directives\WithDirective;
8
use Bavix\Flow\Minify\HTML;
9
use Bavix\Helpers\Arr;
10
use Bavix\Helpers\JSON;
11
use Bavix\Helpers\Str;
12
use Bavix\Lexer\Lexer;
13
use Bavix\Lexer\Token;
14
use Bavix\Lexer\Validator;
15
use JSMin\JSMin;
16
17
class Flow
18
{
19
20
    const VERSION = '1.0.6';
21
22
    /**
23
     * @var string
24
     */
25
    protected $ext;
26
27
    /**
28
     * @var Lexer
29
     */
30
    protected $lexer;
31
32
    /**
33
     * @var Lexeme
34
     */
35
    protected $lexeme;
36
37
    /**
38
     * @var array
39
     */
40
    protected $literals;
41
42
    /**
43
     * @var array
44
     */
45
    protected $printers;
46
47
    /**
48
     * @var array
49
     */
50
    protected $operators;
51
52
    /**
53
     * @var array
54
     */
55
    protected $directives = [];
56
57
    /**
58
     * @var array
59
     */
60
    protected $mapDirectives = [];
61
62
    /**
63
     * @var array
64
     */
65
    protected $constructs = [];
66
67
    /**
68
     * @var array
69
     */
70
    protected $lexemes = [];
71
72
    /**
73
     * @var array
74
     */
75
    protected $folders = [];
76
77
    /**
78
     * @var array
79
     */
80
    protected $rows;
81
82
    /**
83
     * @var Native
84
     */
85
    protected $native;
86
87
    /**
88
     * @var FileSystem
89
     */
90
    protected $fileSystem;
91
92
    /**
93
     * @var string
94
     */
95
    protected $tpl;
96
97
    /**
98
     * @var bool
99
     */
100
    protected $debug;
101
102
    /**
103
     * @var bool
104
     */
105
    protected $minify;
106
107
    /**
108
     * @var array
109
     */
110
    protected $extends;
111
112
    /**
113
     * @var string
114
     */
115
    protected $pathCompile;
116
117
    /**
118
     * Flow constructor.
119
     *
120
     * @param Native $native
121
     * @param array  $options
122
     */
123 24
    public function __construct(Native $native = null, array $options = [])
124
    {
125
        // configs
126 24
        $this->mapDirectives = $options['directives'] ?? [];
127 24
        $this->folders       = $options['folders'] ?? [];
128 24
        $this->lexemes       = $options['lexemes'] ?? [];
129 24
        $this->minify        = $options['minify'] ?? false;
130 24
        $this->extends       = $options['extends'] ?? [];
131 24
        $this->debug         = $options['debug'] ?? false;
132 24
        $this->ext           = $options['ext'] ?? 'bxf';
133
134 24
        Cache::setPool($options['cache'] ?? null);
135
136
        // props
137 24
        $this->constructs = Property::get('constructs');
138
        // /props
139
140 24
        $this->pathCompile = $options['compile'] ?? sys_get_temp_dir();
141
142 24
        $this->setNative($native);
143 24
    }
144
145 13
    protected function loadLexemes(): self
146
    {
147 13
        foreach ($this->lexemes as $folder)
148
        {
149
            $this->lexeme->addFolder($folder);
150
        }
151
152 13
        return $this;
153
    }
154
155
    /**
156
     * @param Lexeme $lexeme
157
     *
158
     * @return $this
159
     */
160 13
    public function setLexeme(Lexeme $lexeme): self
161
    {
162 13
        $this->lexeme = $lexeme;
163
164 13
        return $this->loadLexemes();
165
    }
166
167
    /**
168
     * @param Lexer $lexer
169
     *
170
     * @return $this
171
     */
172
    public function setLexer(Lexer $lexer): self
173
    {
174
        $this->lexer = $lexer;
175
176
        return $this;
177
    }
178
179
    /**
180
     * @return Lexeme
181
     */
182 13
    public function lexeme(): Lexeme
183
    {
184 13
        if (!$this->lexeme)
185
        {
186 13
            $this->setLexeme(new Lexeme($this));
187
        }
188
189 13
        return $this->lexeme;
190
    }
191
192
    /**
193
     * @return Lexer
194
     */
195 20
    public function lexer(): Lexer
196
    {
197 20
        if (!$this->lexer)
198
        {
199 20
            $this->lexer = new Lexer();
200
        }
201
202 20
        return $this->lexer;
203
    }
204
205
    /**
206
     * @return bool
207
     */
208 20
    public function debugMode(): bool
209
    {
210 20
        return $this->debug;
211
    }
212
213
    /**
214
     * @return FileSystem
215
     */
216 20
    public function fileSystem(): FileSystem
217
    {
218 20
        if (!$this->fileSystem)
219
        {
220 20
            $this->fileSystem = new FileSystem(
221 20
                $this,
222 20
                $this->pathCompile
223
            );
224
        }
225
226 20
        return $this->fileSystem;
227
    }
228
229
    /**
230
     * @return string
231
     */
232 20
    public function ext(): string
233
    {
234 20
        return '.' . $this->ext;
235
    }
236
237 24
    protected function setNative($native)
238
    {
239 24
        if ($native)
240
        {
241 20
            $this->native = $native;
242 20
            $this->native->setFlow($this);
243
244 20
            foreach ($this->folders as $folder => $path)
245
            {
246 20
                $this->native->addFolder($folder, $path);
247
            }
248
        }
249 24
    }
250
251
    /**
252
     * @return Native
253
     */
254 20
    public function native(): Native
255
    {
256 20
        if (!$this->native)
257
        {
258 20
            $this->setNative(new Native());
259
        }
260
261 20
        return $this->native;
262
    }
263
264
    /**
265
     * @param array $tokens
266
     *
267
     * @return string
268
     */
269
    protected function fragment(array $tokens): string
270
    {
271 13
        $data = Arr::map($tokens['tokens'] ?? $tokens, function (Token $token) {
272 13
            return $token->token;
273 13
        });
274
275 13
        return \str_replace(
276 13
            '. ',
277 13
            '.',
278 13
            \implode(' ', $data)
279
        );
280
    }
281
282 20
    public function build(array $data): string
283
    {
284 20
        $self      = $this;
285 20
        $_storeKey = __CLASS__ . JSON::encode($data);
286
287 20
        return Cache::get($_storeKey, function () use ($self, &$data) {
288 19
            return $self->buildWithoutCache($data);
289 20
        });
290
    }
291
292
    /**
293
     * @param array $data
294
     *
295
     * @return string
296
     */
297 19
    public function buildWithoutCache(array $data): string
298
    {
299 19
        $code     = [];
300 19
        $lastLast = null;
301 19
        $last     = null;
302
303
        /**
304
         * @var Token $token
305
         * @var Token $last
306
         * @var Token $lastLast
307
         */
308 19
        foreach ($data['tokens'] as $token)
309
        {
310 19
            $_token = clone $token;
311
312 19
            if ($_token->type === T_OBJECT_OPERATOR)
313
            {
314
                throw new Invalid('Undefined object operator `->`!');
315
            }
316
317 19
            if (Arr::in([T_NEW, T_CLONE, T_INSTEADOF, T_INSTANCEOF, T_AS], $_token->type))
318
            {
319
                $lastLast = $last;
320
                $last     = $_token;
321
322
                if (!Arr::in([T_NEW, T_CLASS], $_token->type))
323
                {
324
                    $code[] = ' ';
325
                }
326
327
                $code[] = $_token->token;
328
                $code[] = ' ';
329
                continue;
330
            }
331
332 19
            if ($last && (!$lastLast ||
333 4
                    ($lastLast->type !== T_VARIABLE &&
334 4
                        $lastLast->type !== Validator::T_ENDBRACKET &&
335 19
                        $lastLast->type !== Validator::T_ENDARRAY))
336 19
                && $last->type === Validator::T_DOT)
337
            {
338 1
                $pop = Arr::pop($code);
339 1
                Arr::push($code, '\\' . WithDirective::class . '::last()');
340 1
                Arr::push($code, $pop);
341
            }
342
343 19
            if ($_token->type === Validator::T_CONCAT)
344
            {
345
                $_token->token = '.';
346
            }
347
348 19
            if ((!$last ||
0 ignored issues
show
introduced by Babichev Maxim
Consider adding parentheses for clarity. Current Interpretation: (! $last || $last && ! B...= Bavix\Flow\T_FUNCTION, Probably Intended Meaning: ! $last || ($last && ! B... Bavix\Flow\T_FUNCTION)
Loading history...
349 19
                ($last && !Arr::in([\T_DOUBLE_COLON, Validator::T_DOT], $last->type))) &&
350 19
                $_token->type === T_FUNCTION)
351
            {
352
                if (Str::ucFirst($_token->token) !== $_token->token &&
353
                    !Arr::in($this->constructs, $_token->token))
354
                {
355
                    $_token->token = '$this->helper->' . $_token->token;
356
                }
357
            }
358
359 19
            if (Arr::in([Validator::T_BRACKET, T_ARRAY], $_token->type))
360
            {
361 3
                if ($last && $last->type === Validator::T_DOT)
362
                {
363 1
                    Arr::pop($code);
364
                }
365
            }
366
367 19
            if (Arr::in([T_VARIABLE, T_FUNCTION], $_token->type))
368
            {
369 19
                $_token->token = \str_replace('.', '->', $_token->token);
370
371 19
                if ($last && $last->type === Validator::T_DOT)
372
                {
373
                    Arr::pop($code);
374
                    Arr::push($code, '->');
375
                }
376
377 19
                if (Str::ucFirst($_token->token) === $_token->token)
378
                {
379 1
                    $_token->type = T_CLASS;
380
                }
381
382 19
                if (!Arr::in([T_FUNCTION, T_CLASS], $_token->type) &&
383 19
                    (!$last || !Arr::in([
384 3
                            Validator::T_ENDBRACKET,
385 3
                            Validator::T_ENDARRAY,
386 3
                            Validator::T_DOT,
387 3
                            T_NS_SEPARATOR,
388
                            \T_DOUBLE_COLON
389 19
                        ], $last->type)))
390
                {
391 19
                    $_token->token = '$this->' . $_token->token;
392
                }
393
            }
394
395 19
            $lastLast = $last;
396 19
            $last     = $_token;
397 19
            $code[]   = $_token->token;
398
        }
399
400 19
        return \implode($code);
401
    }
402
403
    /**
404
     * @param string $view
405
     *
406
     * @return string
407
     */
408 20
    protected function minify(string $view): string
409
    {
410 20
        $html = $this->compile($view);
411
412 19
        if ($this->minify)
413
        {
414 1
            $html = \Minify_HTML::minify(\trim($html), [
415 1
                'cssMinifier' => [\Minify_CSSmin::class, 'minify'],
416
                'jsMinifier'  => [JSMin::class, 'minify'],
417
            ]);
418
        }
419
420 19
        if (!empty($this->extends))
421
        {
422 3
            $html = (new HTML($html, $this->extends))
423 3
                ->apply();
424
        }
425
426 19
        return $html;
427
    }
428
429
    /**
430
     * @param string $view
431
     *
432
     * @return string
433
     */
434 20
    public function path(string $view): string
435
    {
436 20
        if (!$this->fileSystem()->has($view))
437
        {
438 20
            $this->fileSystem()->set($view, $this->minify($view));
439
        }
440
441 19
        return $this->fileSystem()->get($view);
442
    }
443
444
    /**
445
     * @param array $rows
446
     * @param bool  $escape
447
     */
448 20
    protected function printers(array $rows, $escape = true)
449
    {
450 20
        $begin = $escape ? '\\htmlspecialchars(' : '';
451 20
        $end   = $escape ? ', ENT_QUOTES, \'UTF-8\')' : '';
452
453 20
        foreach ($rows as $row)
454
        {
455 18
            $this->tpl = $this->replace(
456 18
                $row['code'],
457 18
                '<?php echo ' . $begin . $this->build($row) . $end . '; ?>'
458
            );
459
        }
460 20
    }
461
462
    /**
463
     * @param string $key
464
     * @param array  $data
465
     * @param array  $operator
466
     *
467
     * @return mixed
468
     */
469 13
    protected function directive(string $key, array $data, array $operator)
470
    {
471 13
        $class = __NAMESPACE__ . '\\Directives\\' . Str::ucFirst($key) . 'Directive';
472
473 13
        if (isset($this->mapDirectives[$key]))
474
        {
475
            $class = $this->mapDirectives[$key];
476
        }
477
478 13
        return new $class($this, $data, $operator);
479
    }
480
481
    /**
482
     * @param string    $key
483
     * @param Directive $directive
484
     */
485 13
    protected function pushDirective(string $key, Directive $directive)
486
    {
487 13
        if (empty($this->directives[$key]))
488
        {
489 13
            $this->directives[$key] = [];
490
        }
491
492 13
        $this->directives[$key][] = $directive;
493 13
    }
494
495
    /**
496
     * @param string $key
497
     *
498
     * @return Directive
499
     */
500 7
    protected function popDirective(string $key): Directive
501
    {
502 7
        return Arr::pop($this->directives[$key]);
503
    }
504
505
    /**
506
     * @param string      $fragment
507
     * @param string      $code
508
     * @param string|null $tpl
509
     *
510
     * @return string
511
     */
512 20
    protected function replace(string $fragment, string $code, string $tpl = null): string
513
    {
514 20
        if (!$tpl)
515
        {
516 20
            $tpl = $this->tpl;
517
        }
518
519 20
        return \preg_replace(
520 20
            '~' . \preg_quote($fragment, '~') . '~u',
521 20
            $code,
522 20
            $tpl,
523 20
            1
524
        );
525
    }
526
527
    /**
528
     * @param array  $operator
529
     * @param string $key
530
     *
531
     * @return bool
532
     */
533 13
    protected function ifEnd($operator, string $key): bool
534
    {
535 13
        if (0 === Str::pos($key, 'end'))
536
        {
537 7
            $key  = Str::sub($key, 3);
538 7
            $data = $this->lexeme()->data($key);
539
540 7
            if (true !== $data && $this->lexeme()->closed($key))
541
            {
542 7
                $dir = $this->popDirective($key);
543
544 7
                $this->tpl = $this->replace(
545 7
                    $operator['code'],
546 7
                    $dir->endDirective()
547
                );
548
            }
549
550 7
            return !$data;
551
        }
552
553 13
        return false;
554
    }
555
556 20
    protected function operators()
557
    {
558 20
        foreach ($this->operators as $operator)
559
        {
560
            /**
561
             * @var Token $_token
562
             */
563 13
            $_token = current($operator['tokens']);
564 13
            $data   = $this->lexeme()->data($_token->token);
565
566 13
            $end = !$this->ifEnd($operator, $_token->token);
567
568 13
            if ($end && true !== $data)
569
            {
570 13
                $data = $this->lexeme()->apply(
571 13
                    $_token->token,
572 13
                    $this->fragment($operator)
573
                );
574
575
                /**
576
                 * @var Directive $directive
577
                 */
578 13
                $directive = $this->directive($_token->token, $data ?: [], $operator);
579 13
                $this->pushDirective($_token->token, $directive);
580
581 13
                $this->tpl = $this->replace(
582 13
                    $operator['code'],
583 13
                    $directive->render()
584
                );
585
            }
586
        }
587 20
    }
588
589
    /**
590
     * @param string $view
591
     * @param array  $data
592
     *
593
     * @return string
594
     */
595 20
    public function render(string $view, array $data = []): string
596
    {
597 20
        return $this->native()->render(
598 20
            $this->path($view),
599 19
            $data
600
        );
601
    }
602
603
    /**
604
     * @param string $view
605
     *
606
     * @return string
607
     */
608 20
    public function compile(string $view): string
609
    {
610 20
        $path      = $this->native()->path($view . $this->ext());
611 20
        $this->tpl = \file_get_contents($path);
612 20
        $tokens    = $this->lexer()->tokens($this->tpl);
0 ignored issues
show
Deprecated Code introduced by Babichev Maxim
The function Bavix\Lexer\Lexer::tokens() has been deprecated: use fragments ( Ignorable by Annotation )

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

612
        $tokens    = /** @scrutinizer ignore-deprecated */ $this->lexer()->tokens($this->tpl);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
613
614 20
        $this->literals  = $tokens[Lexer::LITERAL];
615 20
        $this->printers  = $tokens[Lexer::PRINTER];
616 20
        $this->operators = $tokens[Lexer::OPERATOR];
617 20
        $this->rows      = $tokens[Lexer::RAW];
618
619 20
        $this->printers($this->printers);
620 20
        $this->printers($this->rows, false);
621 20
        $this->operators();
622
623
        // check directives
624 20
        foreach ($this->directives as $name => $items)
625
        {
626 13
            if ($this->lexeme()->closed($name))
627
            {
628 8
                if (!empty($items))
629
                {
630 1
                    throw new Runtime(
631 1
                        \sprintf(
632 1
                            'Directive %s not closed',
633 13
                            \get_class(Arr::pop($items))
634
                        )
635
                    );
636
                }
637
            }
638
        }
639
640 19
        foreach ($this->literals as $key => $literal)
641
        {
642
            $this->tpl = \str_replace($key, $literal, $this->tpl);
643
        }
644
645 19
        return $this->tpl;
646
    }
647
648
}
649