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
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
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
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 |