Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\View\Compilers;
4
 
5
use Illuminate\Container\Container;
6
use Illuminate\Contracts\Support\Htmlable;
7
use Illuminate\Contracts\View\Factory as ViewFactory;
8
use Illuminate\Contracts\View\View;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Str;
11
use Illuminate\Support\Traits\ReflectsClosures;
12
use Illuminate\View\Component;
13
use InvalidArgumentException;
14
 
15
class BladeCompiler extends Compiler implements CompilerInterface
16
{
17
    use Concerns\CompilesAuthorizations,
18
        Concerns\CompilesClasses,
19
        Concerns\CompilesComments,
20
        Concerns\CompilesComponents,
21
        Concerns\CompilesConditionals,
22
        Concerns\CompilesEchos,
23
        Concerns\CompilesErrors,
24
        Concerns\CompilesFragments,
25
        Concerns\CompilesHelpers,
26
        Concerns\CompilesIncludes,
27
        Concerns\CompilesInjections,
28
        Concerns\CompilesJson,
29
        Concerns\CompilesJs,
30
        Concerns\CompilesLayouts,
31
        Concerns\CompilesLoops,
32
        Concerns\CompilesRawPhp,
33
        Concerns\CompilesStacks,
399 lars 34
        Concerns\CompilesStyles,
148 lars 35
        Concerns\CompilesTranslations,
36
        ReflectsClosures;
37
 
38
    /**
39
     * All of the registered extensions.
40
     *
41
     * @var array
42
     */
43
    protected $extensions = [];
44
 
45
    /**
46
     * All custom "directive" handlers.
47
     *
48
     * @var array
49
     */
50
    protected $customDirectives = [];
51
 
52
    /**
53
     * All custom "condition" handlers.
54
     *
55
     * @var array
56
     */
57
    protected $conditions = [];
58
 
59
    /**
60
     * All of the registered precompilers.
61
     *
62
     * @var array
63
     */
64
    protected $precompilers = [];
65
 
66
    /**
67
     * The file currently being compiled.
68
     *
69
     * @var string
70
     */
71
    protected $path;
72
 
73
    /**
74
     * All of the available compiler functions.
75
     *
76
     * @var string[]
77
     */
78
    protected $compilers = [
79
        // 'Comments',
80
        'Extensions',
81
        'Statements',
82
        'Echos',
83
    ];
84
 
85
    /**
86
     * Array of opening and closing tags for raw echos.
87
     *
88
     * @var string[]
89
     */
90
    protected $rawTags = ['{!!', '!!}'];
91
 
92
    /**
93
     * Array of opening and closing tags for regular echos.
94
     *
95
     * @var string[]
96
     */
97
    protected $contentTags = ['{{', '}}'];
98
 
99
    /**
100
     * Array of opening and closing tags for escaped echos.
101
     *
102
     * @var string[]
103
     */
104
    protected $escapedTags = ['{{{', '}}}'];
105
 
106
    /**
107
     * The "regular" / legacy echo string format.
108
     *
109
     * @var string
110
     */
111
    protected $echoFormat = 'e(%s)';
112
 
113
    /**
114
     * Array of footer lines to be added to the template.
115
     *
116
     * @var array
117
     */
118
    protected $footer = [];
119
 
120
    /**
121
     * Array to temporarily store the raw blocks found in the template.
122
     *
123
     * @var array
124
     */
125
    protected $rawBlocks = [];
126
 
127
    /**
128
     * The array of anonymous component paths to search for components in.
129
     *
130
     * @var array
131
     */
132
    protected $anonymousComponentPaths = [];
133
 
134
    /**
135
     * The array of anonymous component namespaces to autoload from.
136
     *
137
     * @var array
138
     */
139
    protected $anonymousComponentNamespaces = [];
140
 
141
    /**
142
     * The array of class component aliases and their class names.
143
     *
144
     * @var array
145
     */
146
    protected $classComponentAliases = [];
147
 
148
    /**
149
     * The array of class component namespaces to autoload from.
150
     *
151
     * @var array
152
     */
153
    protected $classComponentNamespaces = [];
154
 
155
    /**
156
     * Indicates if component tags should be compiled.
157
     *
158
     * @var bool
159
     */
160
    protected $compilesComponentTags = true;
161
 
162
    /**
163
     * Compile the view at the given path.
164
     *
165
     * @param  string|null  $path
166
     * @return void
167
     */
168
    public function compile($path = null)
169
    {
170
        if ($path) {
171
            $this->setPath($path);
172
        }
173
 
174
        if (! is_null($this->cachePath)) {
175
            $contents = $this->compileString($this->files->get($this->getPath()));
176
 
177
            if (! empty($this->getPath())) {
178
                $contents = $this->appendFilePath($contents);
179
            }
180
 
181
            $this->ensureCompiledDirectoryExists(
182
                $compiledPath = $this->getCompiledPath($this->getPath())
183
            );
184
 
185
            $this->files->put($compiledPath, $contents);
186
        }
187
    }
188
 
189
    /**
190
     * Append the file path to the compiled string.
191
     *
192
     * @param  string  $contents
193
     * @return string
194
     */
195
    protected function appendFilePath($contents)
196
    {
197
        $tokens = $this->getOpenAndClosingPhpTokens($contents);
198
 
199
        if ($tokens->isNotEmpty() && $tokens->last() !== T_CLOSE_TAG) {
200
            $contents .= ' ?>';
201
        }
202
 
203
        return $contents."<?php /**PATH {$this->getPath()} ENDPATH**/ ?>";
204
    }
205
 
206
    /**
207
     * Get the open and closing PHP tag tokens from the given string.
208
     *
209
     * @param  string  $contents
210
     * @return \Illuminate\Support\Collection
211
     */
212
    protected function getOpenAndClosingPhpTokens($contents)
213
    {
214
        return collect(token_get_all($contents))
215
            ->pluck(0)
216
            ->filter(function ($token) {
217
                return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]);
218
            });
219
    }
220
 
221
    /**
222
     * Get the path currently being compiled.
223
     *
224
     * @return string
225
     */
226
    public function getPath()
227
    {
228
        return $this->path;
229
    }
230
 
231
    /**
232
     * Set the path currently being compiled.
233
     *
234
     * @param  string  $path
235
     * @return void
236
     */
237
    public function setPath($path)
238
    {
239
        $this->path = $path;
240
    }
241
 
242
    /**
243
     * Compile the given Blade template contents.
244
     *
245
     * @param  string  $value
246
     * @return string
247
     */
248
    public function compileString($value)
249
    {
250
        [$this->footer, $result] = [[], ''];
251
 
252
        // First we will compile the Blade component tags. This is a precompile style
253
        // step which compiles the component Blade tags into @component directives
254
        // that may be used by Blade. Then we should call any other precompilers.
255
        $value = $this->compileComponentTags(
256
            $this->compileComments($this->storeUncompiledBlocks($value))
257
        );
258
 
259
        foreach ($this->precompilers as $precompiler) {
260
            $value = $precompiler($value);
261
        }
262
 
263
        // Here we will loop through all of the tokens returned by the Zend lexer and
264
        // parse each one into the corresponding valid PHP. We will then have this
265
        // template as the correctly rendered PHP that can be rendered natively.
266
        foreach (token_get_all($value) as $token) {
267
            $result .= is_array($token) ? $this->parseToken($token) : $token;
268
        }
269
 
270
        if (! empty($this->rawBlocks)) {
271
            $result = $this->restoreRawContent($result);
272
        }
273
 
274
        // If there are any footer lines that need to get added to a template we will
275
        // add them here at the end of the template. This gets used mainly for the
276
        // template inheritance via the extends keyword that should be appended.
277
        if (count($this->footer) > 0) {
278
            $result = $this->addFooters($result);
279
        }
280
 
281
        if (! empty($this->echoHandlers)) {
282
            $result = $this->addBladeCompilerVariable($result);
283
        }
284
 
285
        return str_replace(
286
            ['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'],
287
            '',
288
            $result);
289
    }
290
 
291
    /**
292
     * Evaluate and render a Blade string to HTML.
293
     *
294
     * @param  string  $string
295
     * @param  array  $data
296
     * @param  bool  $deleteCachedView
297
     * @return string
298
     */
299
    public static function render($string, $data = [], $deleteCachedView = false)
300
    {
301
        $component = new class($string) extends Component
302
        {
303
            protected $template;
304
 
305
            public function __construct($template)
306
            {
307
                $this->template = $template;
308
            }
309
 
310
            public function render()
311
            {
312
                return $this->template;
313
            }
314
        };
315
 
316
        $view = Container::getInstance()
317
                    ->make(ViewFactory::class)
318
                    ->make($component->resolveView(), $data);
319
 
320
        return tap($view->render(), function () use ($view, $deleteCachedView) {
321
            if ($deleteCachedView) {
322
                unlink($view->getPath());
323
            }
324
        });
325
    }
326
 
327
    /**
328
     * Render a component instance to HTML.
329
     *
330
     * @param  \Illuminate\View\Component  $component
331
     * @return string
332
     */
333
    public static function renderComponent(Component $component)
334
    {
335
        $data = $component->data();
336
 
337
        $view = value($component->resolveView(), $data);
338
 
339
        if ($view instanceof View) {
340
            return $view->with($data)->render();
341
        } elseif ($view instanceof Htmlable) {
342
            return $view->toHtml();
343
        } else {
344
            return Container::getInstance()
345
                ->make(ViewFactory::class)
346
                ->make($view, $data)
347
                ->render();
348
        }
349
    }
350
 
351
    /**
352
     * Store the blocks that do not receive compilation.
353
     *
354
     * @param  string  $value
355
     * @return string
356
     */
357
    protected function storeUncompiledBlocks($value)
358
    {
359
        if (str_contains($value, '@verbatim')) {
360
            $value = $this->storeVerbatimBlocks($value);
361
        }
362
 
363
        if (str_contains($value, '@php')) {
364
            $value = $this->storePhpBlocks($value);
365
        }
366
 
367
        return $value;
368
    }
369
 
370
    /**
371
     * Store the verbatim blocks and replace them with a temporary placeholder.
372
     *
373
     * @param  string  $value
374
     * @return string
375
     */
376
    protected function storeVerbatimBlocks($value)
377
    {
378
        return preg_replace_callback('/(?<!@)@verbatim(.*?)@endverbatim/s', function ($matches) {
379
            return $this->storeRawBlock($matches[1]);
380
        }, $value);
381
    }
382
 
383
    /**
384
     * Store the PHP blocks and replace them with a temporary placeholder.
385
     *
386
     * @param  string  $value
387
     * @return string
388
     */
389
    protected function storePhpBlocks($value)
390
    {
391
        return preg_replace_callback('/(?<!@)@php(.*?)@endphp/s', function ($matches) {
392
            return $this->storeRawBlock("<?php{$matches[1]}?>");
393
        }, $value);
394
    }
395
 
396
    /**
397
     * Store a raw block and return a unique raw placeholder.
398
     *
399
     * @param  string  $value
400
     * @return string
401
     */
402
    protected function storeRawBlock($value)
403
    {
404
        return $this->getRawPlaceholder(
405
            array_push($this->rawBlocks, $value) - 1
406
        );
407
    }
408
 
409
    /**
410
     * Compile the component tags.
411
     *
412
     * @param  string  $value
413
     * @return string
414
     */
415
    protected function compileComponentTags($value)
416
    {
417
        if (! $this->compilesComponentTags) {
418
            return $value;
419
        }
420
 
421
        return (new ComponentTagCompiler(
422
            $this->classComponentAliases, $this->classComponentNamespaces, $this
423
        ))->compile($value);
424
    }
425
 
426
    /**
427
     * Replace the raw placeholders with the original code stored in the raw blocks.
428
     *
429
     * @param  string  $result
430
     * @return string
431
     */
432
    protected function restoreRawContent($result)
433
    {
434
        $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) {
435
            return $this->rawBlocks[$matches[1]];
436
        }, $result);
437
 
438
        $this->rawBlocks = [];
439
 
440
        return $result;
441
    }
442
 
443
    /**
444
     * Get a placeholder to temporarily mark the position of raw blocks.
445
     *
446
     * @param  int|string  $replace
447
     * @return string
448
     */
449
    protected function getRawPlaceholder($replace)
450
    {
451
        return str_replace('#', $replace, '@__raw_block_#__@');
452
    }
453
 
454
    /**
455
     * Add the stored footers onto the given content.
456
     *
457
     * @param  string  $result
458
     * @return string
459
     */
460
    protected function addFooters($result)
461
    {
462
        return ltrim($result, "\n")
463
                ."\n".implode("\n", array_reverse($this->footer));
464
    }
465
 
466
    /**
467
     * Parse the tokens from the template.
468
     *
469
     * @param  array  $token
470
     * @return string
471
     */
472
    protected function parseToken($token)
473
    {
474
        [$id, $content] = $token;
475
 
476
        if ($id == T_INLINE_HTML) {
477
            foreach ($this->compilers as $type) {
478
                $content = $this->{"compile{$type}"}($content);
479
            }
480
        }
481
 
482
        return $content;
483
    }
484
 
485
    /**
486
     * Execute the user defined extensions.
487
     *
488
     * @param  string  $value
489
     * @return string
490
     */
491
    protected function compileExtensions($value)
492
    {
493
        foreach ($this->extensions as $compiler) {
494
            $value = $compiler($value, $this);
495
        }
496
 
497
        return $value;
498
    }
499
 
500
    /**
501
     * Compile Blade statements that start with "@".
502
     *
503
     * @param  string  $template
504
     * @return string
505
     */
506
    protected function compileStatements($template)
507
    {
508
        preg_match_all('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( [\S\s]*? ) \))?/x', $template, $matches);
509
 
510
        for ($i = 0; isset($matches[0][$i]); $i++) {
511
            $match = [
512
                $matches[0][$i],
513
                $matches[1][$i],
514
                $matches[2][$i],
515
                $matches[3][$i] ?: null,
516
                $matches[4][$i] ?: null,
517
            ];
518
 
519
            // Here we check to see if we have properly found the closing parenthesis by
520
            // regex pattern or not, and will recursively continue on to the next ")"
521
            // then check again until the tokenizer confirms we find the right one.
522
            while (isset($match[4]) &&
523
                   Str::endsWith($match[0], ')') &&
524
                   ! $this->hasEvenNumberOfParentheses($match[0])) {
399 lars 525
                if (($after = Str::after($template, $match[0])) === $template) {
526
                    break;
527
                }
148 lars 528
 
399 lars 529
                $rest = Str::before($after, ')');
530
 
531
                if (isset($matches[0][$i + 1]) && Str::contains($rest.')', $matches[0][$i + 1])) {
532
                    unset($matches[0][$i + 1]);
533
                    $i++;
534
                }
535
 
148 lars 536
                $match[0] = $match[0].$rest.')';
537
                $match[3] = $match[3].$rest.')';
538
                $match[4] = $match[4].$rest;
539
            }
540
 
541
            $template = Str::replaceFirst($match[0], $this->compileStatement($match), $template);
542
        }
543
 
544
        return $template;
545
    }
546
 
547
    /**
548
     * Determine if the given expression has the same number of opening and closing parentheses.
549
     *
550
     * @param  string  $expression
551
     * @return bool
552
     */
553
    protected function hasEvenNumberOfParentheses(string $expression)
554
    {
555
        $tokens = token_get_all('<?php '.$expression);
556
 
557
        if (Arr::last($tokens) !== ')') {
558
            return false;
559
        }
560
 
561
        $opening = 0;
562
        $closing = 0;
563
 
564
        foreach ($tokens as $token) {
565
            if ($token == ')') {
566
                $closing++;
567
            } elseif ($token == '(') {
568
                $opening++;
569
            }
570
        }
571
 
572
        return $opening === $closing;
573
    }
574
 
575
    /**
576
     * Compile a single Blade @ statement.
577
     *
578
     * @param  array  $match
579
     * @return string
580
     */
581
    protected function compileStatement($match)
582
    {
583
        if (str_contains($match[1], '@')) {
584
            $match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1];
585
        } elseif (isset($this->customDirectives[$match[1]])) {
586
            $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3));
587
        } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) {
588
            $match[0] = $this->$method(Arr::get($match, 3));
589
        } else {
590
            return $match[0];
591
        }
592
 
593
        return isset($match[3]) ? $match[0] : $match[0].$match[2];
594
    }
595
 
596
    /**
597
     * Call the given directive with the given value.
598
     *
599
     * @param  string  $name
600
     * @param  string|null  $value
601
     * @return string
602
     */
603
    protected function callCustomDirective($name, $value)
604
    {
605
        $value ??= '';
606
 
607
        if (str_starts_with($value, '(') && str_ends_with($value, ')')) {
608
            $value = Str::substr($value, 1, -1);
609
        }
610
 
611
        return call_user_func($this->customDirectives[$name], trim($value));
612
    }
613
 
614
    /**
615
     * Strip the parentheses from the given expression.
616
     *
617
     * @param  string  $expression
618
     * @return string
619
     */
620
    public function stripParentheses($expression)
621
    {
622
        if (Str::startsWith($expression, '(')) {
623
            $expression = substr($expression, 1, -1);
624
        }
625
 
626
        return $expression;
627
    }
628
 
629
    /**
630
     * Register a custom Blade compiler.
631
     *
632
     * @param  callable  $compiler
633
     * @return void
634
     */
635
    public function extend(callable $compiler)
636
    {
637
        $this->extensions[] = $compiler;
638
    }
639
 
640
    /**
641
     * Get the extensions used by the compiler.
642
     *
643
     * @return array
644
     */
645
    public function getExtensions()
646
    {
647
        return $this->extensions;
648
    }
649
 
650
    /**
651
     * Register an "if" statement directive.
652
     *
653
     * @param  string  $name
654
     * @param  callable  $callback
655
     * @return void
656
     */
657
    public function if($name, callable $callback)
658
    {
659
        $this->conditions[$name] = $callback;
660
 
661
        $this->directive($name, function ($expression) use ($name) {
662
            return $expression !== ''
663
                    ? "<?php if (\Illuminate\Support\Facades\Blade::check('{$name}', {$expression})): ?>"
664
                    : "<?php if (\Illuminate\Support\Facades\Blade::check('{$name}')): ?>";
665
        });
666
 
667
        $this->directive('unless'.$name, function ($expression) use ($name) {
668
            return $expression !== ''
669
                ? "<?php if (! \Illuminate\Support\Facades\Blade::check('{$name}', {$expression})): ?>"
670
                : "<?php if (! \Illuminate\Support\Facades\Blade::check('{$name}')): ?>";
671
        });
672
 
673
        $this->directive('else'.$name, function ($expression) use ($name) {
674
            return $expression !== ''
675
                ? "<?php elseif (\Illuminate\Support\Facades\Blade::check('{$name}', {$expression})): ?>"
676
                : "<?php elseif (\Illuminate\Support\Facades\Blade::check('{$name}')): ?>";
677
        });
678
 
679
        $this->directive('end'.$name, function () {
680
            return '<?php endif; ?>';
681
        });
682
    }
683
 
684
    /**
685
     * Check the result of a condition.
686
     *
687
     * @param  string  $name
399 lars 688
     * @param  array  ...$parameters
148 lars 689
     * @return bool
690
     */
691
    public function check($name, ...$parameters)
692
    {
693
        return call_user_func($this->conditions[$name], ...$parameters);
694
    }
695
 
696
    /**
697
     * Register a class-based component alias directive.
698
     *
699
     * @param  string  $class
700
     * @param  string|null  $alias
701
     * @param  string  $prefix
702
     * @return void
703
     */
704
    public function component($class, $alias = null, $prefix = '')
705
    {
706
        if (! is_null($alias) && str_contains($alias, '\\')) {
707
            [$class, $alias] = [$alias, $class];
708
        }
709
 
710
        if (is_null($alias)) {
711
            $alias = str_contains($class, '\\View\\Components\\')
712
                            ? collect(explode('\\', Str::after($class, '\\View\\Components\\')))->map(function ($segment) {
713
                                return Str::kebab($segment);
714
                            })->implode(':')
715
                            : Str::kebab(class_basename($class));
716
        }
717
 
718
        if (! empty($prefix)) {
719
            $alias = $prefix.'-'.$alias;
720
        }
721
 
722
        $this->classComponentAliases[$alias] = $class;
723
    }
724
 
725
    /**
726
     * Register an array of class-based components.
727
     *
728
     * @param  array  $components
729
     * @param  string  $prefix
730
     * @return void
731
     */
732
    public function components(array $components, $prefix = '')
733
    {
734
        foreach ($components as $key => $value) {
735
            if (is_numeric($key)) {
736
                $this->component($value, null, $prefix);
737
            } else {
738
                $this->component($key, $value, $prefix);
739
            }
740
        }
741
    }
742
 
743
    /**
744
     * Get the registered class component aliases.
745
     *
746
     * @return array
747
     */
748
    public function getClassComponentAliases()
749
    {
750
        return $this->classComponentAliases;
751
    }
752
 
753
    /**
754
     * Register a new anonymous component path.
755
     *
756
     * @param  string  $path
757
     * @param  string|null  $prefix
758
     * @return void
759
     */
760
    public function anonymousComponentPath(string $path, string $prefix = null)
761
    {
762
        $prefixHash = md5($prefix ?: $path);
763
 
764
        $this->anonymousComponentPaths[] = [
765
            'path' => $path,
766
            'prefix' => $prefix,
767
            'prefixHash' => $prefixHash,
768
        ];
769
 
770
        Container::getInstance()
771
                ->make(ViewFactory::class)
772
                ->addNamespace($prefixHash, $path);
773
    }
774
 
775
    /**
776
     * Register an anonymous component namespace.
777
     *
778
     * @param  string  $directory
779
     * @param  string|null  $prefix
780
     * @return void
781
     */
782
    public function anonymousComponentNamespace(string $directory, string $prefix = null)
783
    {
784
        $prefix ??= $directory;
785
 
786
        $this->anonymousComponentNamespaces[$prefix] = Str::of($directory)
787
                ->replace('/', '.')
788
                ->trim('. ')
789
                ->toString();
790
    }
791
 
792
    /**
793
     * Register a class-based component namespace.
794
     *
795
     * @param  string  $namespace
796
     * @param  string  $prefix
797
     * @return void
798
     */
799
    public function componentNamespace($namespace, $prefix)
800
    {
801
        $this->classComponentNamespaces[$prefix] = $namespace;
802
    }
803
 
804
    /**
805
     * Get the registered anonymous component paths.
806
     *
807
     * @return array
808
     */
809
    public function getAnonymousComponentPaths()
810
    {
811
        return $this->anonymousComponentPaths;
812
    }
813
 
814
    /**
815
     * Get the registered anonymous component namespaces.
816
     *
817
     * @return array
818
     */
819
    public function getAnonymousComponentNamespaces()
820
    {
821
        return $this->anonymousComponentNamespaces;
822
    }
823
 
824
    /**
825
     * Get the registered class component namespaces.
826
     *
827
     * @return array
828
     */
829
    public function getClassComponentNamespaces()
830
    {
831
        return $this->classComponentNamespaces;
832
    }
833
 
834
    /**
835
     * Register a component alias directive.
836
     *
837
     * @param  string  $path
838
     * @param  string|null  $alias
839
     * @return void
840
     */
841
    public function aliasComponent($path, $alias = null)
842
    {
843
        $alias = $alias ?: Arr::last(explode('.', $path));
844
 
845
        $this->directive($alias, function ($expression) use ($path) {
846
            return $expression
847
                        ? "<?php \$__env->startComponent('{$path}', {$expression}); ?>"
848
                        : "<?php \$__env->startComponent('{$path}'); ?>";
849
        });
850
 
851
        $this->directive('end'.$alias, function ($expression) {
852
            return '<?php echo $__env->renderComponent(); ?>';
853
        });
854
    }
855
 
856
    /**
857
     * Register an include alias directive.
858
     *
859
     * @param  string  $path
860
     * @param  string|null  $alias
861
     * @return void
862
     */
863
    public function include($path, $alias = null)
864
    {
865
        $this->aliasInclude($path, $alias);
866
    }
867
 
868
    /**
869
     * Register an include alias directive.
870
     *
871
     * @param  string  $path
872
     * @param  string|null  $alias
873
     * @return void
874
     */
875
    public function aliasInclude($path, $alias = null)
876
    {
877
        $alias = $alias ?: Arr::last(explode('.', $path));
878
 
879
        $this->directive($alias, function ($expression) use ($path) {
880
            $expression = $this->stripParentheses($expression) ?: '[]';
881
 
882
            return "<?php echo \$__env->make('{$path}', {$expression}, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
883
        });
884
    }
885
 
886
    /**
887
     * Register a handler for custom directives.
888
     *
889
     * @param  string  $name
890
     * @param  callable  $handler
891
     * @return void
892
     *
893
     * @throws \InvalidArgumentException
894
     */
895
    public function directive($name, callable $handler)
896
    {
897
        if (! preg_match('/^\w+(?:::\w+)?$/x', $name)) {
898
            throw new InvalidArgumentException("The directive name [{$name}] is not valid. Directive names must only contain alphanumeric characters and underscores.");
899
        }
900
 
901
        $this->customDirectives[$name] = $handler;
902
    }
903
 
904
    /**
905
     * Get the list of custom directives.
906
     *
907
     * @return array
908
     */
909
    public function getCustomDirectives()
910
    {
911
        return $this->customDirectives;
912
    }
913
 
914
    /**
915
     * Register a new precompiler.
916
     *
917
     * @param  callable  $precompiler
918
     * @return void
919
     */
920
    public function precompiler(callable $precompiler)
921
    {
922
        $this->precompilers[] = $precompiler;
923
    }
924
 
925
    /**
926
     * Set the echo format to be used by the compiler.
927
     *
928
     * @param  string  $format
929
     * @return void
930
     */
931
    public function setEchoFormat($format)
932
    {
933
        $this->echoFormat = $format;
934
    }
935
 
936
    /**
937
     * Set the "echo" format to double encode entities.
938
     *
939
     * @return void
940
     */
941
    public function withDoubleEncoding()
942
    {
943
        $this->setEchoFormat('e(%s, true)');
944
    }
945
 
946
    /**
947
     * Set the "echo" format to not double encode entities.
948
     *
949
     * @return void
950
     */
951
    public function withoutDoubleEncoding()
952
    {
953
        $this->setEchoFormat('e(%s, false)');
954
    }
955
 
956
    /**
957
     * Indicate that component tags should not be compiled.
958
     *
959
     * @return void
960
     */
961
    public function withoutComponentTags()
962
    {
963
        $this->compilesComponentTags = false;
964
    }
965
}