├── .gitignore
├── tests
├── View
│ ├── fixtures
│ │ ├── basic.php
│ │ ├── nested
│ │ │ ├── basic.php
│ │ │ └── child.php
│ │ ├── namespaced
│ │ │ └── basic.php
│ │ ├── section-exception-layout.php
│ │ ├── component.php
│ │ └── section-exception.php
│ ├── DataObjectStub.php
│ ├── Blade
│ │ ├── BladeShowTest.php
│ │ ├── BladeAppendTest.php
│ │ ├── BladeStopSectionTest.php
│ │ ├── BladeEndSectionsTest.php
│ │ ├── BladeOverwriteSectionTest.php
│ │ ├── BladeStackTest.php
│ │ ├── BladeUnsetStatementsTest.php
│ │ ├── BladePushTest.php
│ │ ├── BladeIfEmptyStatementsTest.php
│ │ ├── BladeIfIssetStatementsTest.php
│ │ ├── BladeSectionTest.php
│ │ ├── BladeEachTest.php
│ │ ├── BladePrependTest.php
│ │ ├── BladeUnlessStatementsTest.php
│ │ ├── BladeHasSectionTest.php
│ │ ├── BladeElseIfStatementsTest.php
│ │ ├── BladeYieldTest.php
│ │ ├── BladeIncludeTest.php
│ │ ├── BladeCanStatementsTest.php
│ │ ├── BladeIncludeIfTest.php
│ │ ├── BladeIncludeWhenTest.php
│ │ ├── BladeIncludeFirstTest.php
│ │ ├── BladeCannotStatementsTest.php
│ │ ├── AbstractBladeTestCase.php
│ │ ├── BladeHelpersTest.php
│ │ ├── BladeCananyStatementsTest.php
│ │ ├── BladeJsonTest.php
│ │ ├── BladeEscapedTest.php
│ │ ├── BladeLangTest.php
│ │ ├── BladeWhileStatementsTest.php
│ │ ├── BladeCommentsTest.php
│ │ ├── BladeIfGuestStatementsTest.php
│ │ ├── BladeElseStatementsTest.php
│ │ ├── BladeForStatementsTest.php
│ │ ├── BladeExpressionTest.php
│ │ ├── BladeElseGuestStatementsTest.php
│ │ ├── BladeIfStatementsTest.php
│ │ ├── BladeIfAuthStatementsTest.php
│ │ ├── BladeElseAuthStatementsTest.php
│ │ ├── BladeExtendsTest.php
│ │ ├── BladePhpStatementsTest.php
│ │ ├── BladeBreakStatementsTest.php
│ │ ├── BladeContinueStatementsTest.php
│ │ ├── BladeVerbatimTest.php
│ │ ├── BladeForelseStatementsTest.php
│ │ ├── BladeEchoTest.php
│ │ ├── BladeForeachStatementsTest.php
│ │ └── BladeCustomTest.php
│ ├── ViewCompilerEngineTest.php
│ ├── ViewStringBladeCompilerTest.php
│ ├── ViewTest.php
│ ├── StringViewTest.php
│ └── ViewFactoryTest.php
└── bootstrap.php
├── config
└── blade.php
├── composer.json
├── LICENSE
├── phpunit.xml
├── src
├── Facades
│ └── StringBlade.php
├── View.php
├── Engines
│ └── CompilerEngine.php
├── Compilers
│ ├── BladeCompiler.php
│ └── StringBladeCompiler.php
├── StringView.php
├── Factory.php
└── StringBladeServiceProvider.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.netbeans/
2 | /bkp/
3 | vendor/
--------------------------------------------------------------------------------
/tests/View/fixtures/basic.php:
--------------------------------------------------------------------------------
1 |
2 | Hello World
3 |
--------------------------------------------------------------------------------
/tests/View/fixtures/nested/basic.php:
--------------------------------------------------------------------------------
1 | Hello World
2 |
--------------------------------------------------------------------------------
/tests/View/fixtures/namespaced/basic.php:
--------------------------------------------------------------------------------
1 | Hello World
2 |
--------------------------------------------------------------------------------
/tests/View/fixtures/nested/child.php:
--------------------------------------------------------------------------------
1 | Hello World
2 |
--------------------------------------------------------------------------------
/tests/View/fixtures/section-exception-layout.php:
--------------------------------------------------------------------------------
1 | yieldContent('content'); ?>
2 |
--------------------------------------------------------------------------------
/tests/View/fixtures/component.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/View/DataObjectStub.php:
--------------------------------------------------------------------------------
1 | make('layout', \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>
2 | startSection('content'); ?>
3 |
4 | stopSection(); ?>
5 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeShowTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('yieldSection(); ?>', $this->compiler->compileString('@show'));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeAppendTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('appendSection(); ?>', $this->compiler->compileString('@append'));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeStopSectionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('stopSection(); ?>', $this->compiler->compileString('@stop'));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeEndSectionsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('stopSection(); ?>', $this->compiler->compileString('@endsection'));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeOverwriteSectionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('stopSection(true); ?>', $this->compiler->compileString('@overwrite'));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeStackTest.php:
--------------------------------------------------------------------------------
1 | yieldPushContent(\'foo\'); ?>';
11 | $this->assertEquals($expected, $this->compiler->compileString($string));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeUnsetStatementsTest.php:
--------------------------------------------------------------------------------
1 | ';
11 | $this->assertEquals($expected, $this->compiler->compileString($string));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/config/blade.php:
--------------------------------------------------------------------------------
1 | env('STRING_BLADE_CACHE_TIMEOUT', 300),
8 |
9 | // Determine whether the service provider to autoload blade custom directives.
10 | 'autoload_custom_directives' => env('STRING_BLADE_AUTOLOAD', false),
11 | ];
--------------------------------------------------------------------------------
/tests/View/Blade/BladePushTest.php:
--------------------------------------------------------------------------------
1 | startPush(\'foo\'); ?>
13 | test
14 | stopPush(); ?>';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIfEmptyStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | breeze
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIfIssetStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | breeze
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeSectionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('startSection(\'foo\'); ?>', $this->compiler->compileString('@section(\'foo\')'));
10 | $this->assertEquals('startSection(name(foo)); ?>', $this->compiler->compileString('@section(name(foo))'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeEachTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('renderEach(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@each(\'foo\', \'bar\')'));
10 | $this->assertEquals('renderEach(name(foo)); ?>', $this->compiler->compileString('@each(name(foo))'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladePrependTest.php:
--------------------------------------------------------------------------------
1 | startPrepend(\'foo\'); ?>
13 | bar
14 | stopPrepend(); ?>';
15 |
16 | $this->assertEquals($expected, $this->compiler->compileString($string));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeUnlessStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | breeze
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeHasSectionTest.php:
--------------------------------------------------------------------------------
1 | yieldContent("section")))): ?>
13 | breeze
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeElseIfStatementsTest.php:
--------------------------------------------------------------------------------
1 |
15 | breeze
16 |
17 | boom
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeYieldTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('yieldContent(\'foo\'); ?>', $this->compiler->compileString('@yield(\'foo\')'));
10 | $this->assertEquals('yieldContent(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@yield(\'foo\', \'bar\')'));
11 | $this->assertEquals('yieldContent(name(foo)); ?>', $this->compiler->compileString('@yield(name(foo))'));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIncludeTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'foo\')'));
10 | $this->assertEquals('make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(name(foo))'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeCanStatementsTest.php:
--------------------------------------------------------------------------------
1 | check(\'update\', [$post])): ?>
15 | breeze
16 | check(\'delete\', [$post])): ?>
17 | sneeze
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIncludeIfTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('exists(\'foo\')) echo $__env->make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeIf(\'foo\')'));
10 | $this->assertEquals('exists(name(foo))) echo $__env->make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeIf(name(foo))'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIncludeWhenTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('renderWhen(true, \'foo\', ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\', ["foo" => "bar"])'));
10 | $this->assertEquals('renderWhen(true, \'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeWhen(true, \'foo\')'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIncludeFirstTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('first(["one", "two"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"])'));
10 | $this->assertEquals('first(["one", "two"], ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"], ["foo" => "bar"])'));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeCannotStatementsTest.php:
--------------------------------------------------------------------------------
1 | denies(\'update\', [$post])): ?>
15 | breeze
16 | denies(\'delete\', [$post])): ?>
17 | sneeze
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/View/Blade/AbstractBladeTestCase.php:
--------------------------------------------------------------------------------
1 | compiler = new BladeCompiler(m::mock(Filesystem::class), __DIR__);
18 | parent::setUp();
19 | }
20 |
21 | protected function tearDown(): void
22 | {
23 | m::close();
24 |
25 | parent::tearDown();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeHelpersTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('', $this->compiler->compileString('@csrf'));
10 | $this->assertEquals('', $this->compiler->compileString("@method('patch')"));
11 | $this->assertEquals('', $this->compiler->compileString('@dd($var1)'));
12 | $this->assertEquals('', $this->compiler->compileString('@dd($var1, $var2)'));
13 | $this->assertEquals('', $this->compiler->compileString('@dump($var1, $var2)'));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeCananyStatementsTest.php:
--------------------------------------------------------------------------------
1 | any([\'create\', \'update\'], [$post])): ?>
15 | breeze
16 | any([\'delete\', \'approve\'], [$post])): ?>
17 | sneeze
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeJsonTest.php:
--------------------------------------------------------------------------------
1 | ;';
11 |
12 | $this->assertEquals($expected, $this->compiler->compileString($string));
13 | }
14 |
15 | public function testEncodingOptionsCanBeOverwritten()
16 | {
17 | $string = 'var foo = @json($var, JSON_HEX_TAG);';
18 | $expected = 'var foo = ;';
19 |
20 | $this->assertEquals($expected, $this->compiler->compileString($string));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeEscapedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('@foreach', $this->compiler->compileString('@@foreach'));
10 | $this->assertEquals('@verbatim @continue @endverbatim', $this->compiler->compileString('@@verbatim @@continue @@endverbatim'));
11 | $this->assertEquals('@foreach($i as $x)', $this->compiler->compileString('@@foreach($i as $x)'));
12 | $this->assertEquals('@continue @break', $this->compiler->compileString('@@continue @@break'));
13 | $this->assertEquals('@foreach(
14 | $i as $x
15 | )', $this->compiler->compileString('@@foreach(
16 | $i as $x
17 | )'));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeLangTest.php:
--------------------------------------------------------------------------------
1 | getFromJson(function_call('foo(blah)')); ?> bar";
11 | $this->assertEquals($expected, $this->compiler->compileString($string));
12 | }
13 |
14 | public function testLanguageAndChoicesAreCompiled()
15 | {
16 | $this->assertEquals('getFromJson(\'foo\'); ?>', $this->compiler->compileString("@lang('foo')"));
17 | $this->assertEquals('choice(\'foo\', 1); ?>', $this->compiler->compileString("@choice('foo', 1)"));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeWhileStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | test
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 |
18 | public function testNestedWhileStatementsAreCompiled()
19 | {
20 | $string = '@while ($foo)
21 | @while ($bar)
22 | test
23 | @endwhile
24 | @endwhile';
25 | $expected = '
26 |
27 | test
28 |
29 | ';
30 | $this->assertEquals($expected, $this->compiler->compileString($string));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeCommentsTest.php:
--------------------------------------------------------------------------------
1 | assertEmpty($this->compiler->compileString($string));
11 |
12 | $string = '{{--
13 | this is a comment
14 | --}}';
15 | $this->assertEmpty($this->compiler->compileString($string));
16 |
17 | $string = sprintf('{{-- this is an %s long comment --}}', str_repeat('extremely ', 1000));
18 | $this->assertEmpty($this->compiler->compileString($string));
19 | }
20 |
21 | public function testBladeCodeInsideCommentsIsNotCompiled()
22 | {
23 | $string = '{{-- @foreach() --}}';
24 |
25 | $this->assertEmpty($this->compiler->compileString($string));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIfGuestStatementsTest.php:
--------------------------------------------------------------------------------
1 | getFiles(), __DIR__);
20 | $string = '@guest("api")
21 | breeze
22 | @endguest';
23 | $expected = 'guard("api")->guest()): ?>
24 | breeze
25 | ';
26 | $this->assertEquals($expected, $compiler->compileString($string));
27 | }
28 |
29 | protected function getFiles()
30 | {
31 | return m::mock(Filesystem::class);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeElseStatementsTest.php:
--------------------------------------------------------------------------------
1 |
15 | breeze
16 |
17 | boom
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 |
22 | public function testElseIfStatementsAreCompiled()
23 | {
24 | $string = '@if(name(foo(bar)))
25 | breeze
26 | @elseif(boom(breeze))
27 | boom
28 | @endif';
29 | $expected = '
30 | breeze
31 |
32 | boom
33 | ';
34 | $this->assertEquals($expected, $this->compiler->compileString($string));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeForStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | test
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 |
18 | public function testNestedForStatementsAreCompiled()
19 | {
20 | $string = '@for ($i = 0; $i < 10; $i++)
21 | @for ($j = 0; $j < 20; $j++)
22 | test
23 | @endfor
24 | @endfor';
25 | $expected = '
26 |
27 | test
28 |
29 | ';
30 | $this->assertEquals($expected, $this->compiler->compileString($string));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wpb/string-blade-compiler",
3 | "description": "Laravel Blade String Compiler, render string as blade templates",
4 | "keywords": ["laravel", "blade", "compiler"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Terre Porter",
9 | "email": "tporter@webpage-builders.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^7.2|^8.0",
14 | "laravel/framework": "^6.0|^7.0|^8.0|^9.0|^10.0"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "Wpb\\String_Blade_Compiler\\": "src/"
19 | }
20 | },
21 | "minimum-stability": "dev",
22 | "extra": {
23 | "laravel": {
24 | "providers": [
25 | "Wpb\\String_Blade_Compiler\\StringBladeServiceProvider"
26 | ],
27 | "aliases": {
28 | "StringBlade": "Wpb\\String_Blade_Compiler\\Facades\\StringBlade"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeExpressionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('getFromJson(foo(bar(baz(qux(breeze()))))); ?> space () getFromJson(foo(bar)); ?>', $this->compiler->compileString('@lang(foo(bar(baz(qux(breeze()))))) space () @lang(foo(bar))'));
10 | }
11 |
12 | public function testExpressionWithinHTML()
13 | {
14 | $this->assertEquals('>', $this->compiler->compileString(''));
15 | $this->assertEquals('>', $this->compiler->compileString(''));
16 | $this->assertEquals(' getFromJson(\'foo\'); ?>>', $this->compiler->compileString(''));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeElseGuestStatementsTest.php:
--------------------------------------------------------------------------------
1 | getFiles(), __DIR__);
20 | $string = '@guest("api")
21 | breeze
22 | @elseguest("standard")
23 | wheeze
24 | @endguest';
25 | $expected = 'guard("api")->guest()): ?>
26 | breeze
27 | guard("standard")->guest()): ?>
28 | wheeze
29 | ';
30 | $this->assertEquals($expected, $compiler->compileString($string));
31 | }
32 |
33 | protected function getFiles()
34 | {
35 | return m::mock(Filesystem::class);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Terre Porter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 | ./tests
18 |
19 |
20 |
21 |
22 | ./src
23 |
24 | ./src/
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeIfStatementsTest.php:
--------------------------------------------------------------------------------
1 |
13 | breeze
14 | ';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 |
18 | public function testSwitchstatementsAreCompiled()
19 | {
20 | $string = '@switch(true)
21 | @case(1)
22 | foo
23 |
24 | @case(2)
25 | bar
26 | @endswitch
27 |
28 | foo
29 |
30 | @switch(true)
31 | @case(1)
32 | foo
33 |
34 | @case(2)
35 | bar
36 | @endswitch';
37 | $expected = '
39 | foo
40 |
41 |
42 | bar
43 |
44 |
45 | foo
46 |
47 |
49 | foo
50 |
51 |
52 | bar
53 | ';
54 | $this->assertEquals($expected, $this->compiler->compileString($string));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | getFiles(), __DIR__);
20 | $string = '@auth("api")
21 | breeze
22 | @endauth';
23 | $expected = 'guard("api")->check()): ?>
24 | breeze
25 | ';
26 | $this->assertEquals($expected, $compiler->compileString($string));
27 | }
28 |
29 | public function testPlainIfStatementsAreCompiled()
30 | {
31 | $compiler = new BladeCompiler($this->getFiles(), __DIR__);
32 | $string = '@auth
33 | breeze
34 | @endauth';
35 | $expected = 'guard()->check()): ?>
36 | breeze
37 | ';
38 | $this->assertEquals($expected, $compiler->compileString($string));
39 | }
40 |
41 | protected function getFiles()
42 | {
43 | return m::mock(Filesystem::class);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Facades/StringBlade.php:
--------------------------------------------------------------------------------
1 | getEngineResolver()->resolve('stringblade')->getCompiler();
30 | //return static::$app['view']->getEngineResolver()->resolve('stringblade')->getCompiler();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeElseAuthStatementsTest.php:
--------------------------------------------------------------------------------
1 | getFiles(), __DIR__);
20 | $string = '@auth("api")
21 | breeze
22 | @elseauth("standard")
23 | wheeze
24 | @endauth';
25 | $expected = 'guard("api")->check()): ?>
26 | breeze
27 | guard("standard")->check()): ?>
28 | wheeze
29 | ';
30 | $this->assertEquals($expected, $compiler->compileString($string));
31 | }
32 |
33 | public function testPlainElseAuthStatementsAreCompiled()
34 | {
35 | $compiler = new BladeCompiler($this->getFiles(), __DIR__);
36 | $string = '@auth("api")
37 | breeze
38 | @elseauth
39 | wheeze
40 | @endauth';
41 | $expected = 'guard("api")->check()): ?>
42 | breeze
43 | guard()->check()): ?>
44 | wheeze
45 | ';
46 | $this->assertEquals($expected, $compiler->compileString($string));
47 | }
48 |
49 | protected function getFiles()
50 | {
51 | return m::mock(Filesystem::class);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/View.php:
--------------------------------------------------------------------------------
1 | view = $view;
33 | $this->path = $path;
34 | $this->engine = $engine;
35 | $this->factory = $factory;
36 |
37 | $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data;
38 |
39 | }
40 |
41 | /**
42 | * Dynamically bind parameters to the view.
43 | *
44 | * @param string $method
45 | * @param array $parameters
46 | * @return \Illuminate\View\View
47 | *
48 | * @throws \BadMethodCallException
49 | */
50 | public function __call($method, $parameters)
51 | {
52 | return parent::__call($method, $parameters);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeExtendsTest.php:
--------------------------------------------------------------------------------
1 | make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
12 | $this->assertEquals($expected, $this->compiler->compileString($string));
13 |
14 | $string = '@extends(name(foo))'.PHP_EOL.'test';
15 | $expected = 'test'.PHP_EOL.'make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
16 | $this->assertEquals($expected, $this->compiler->compileString($string));
17 | }
18 |
19 | public function testSequentialCompileStringCalls()
20 | {
21 | $string = '@extends(\'foo\')
22 | test';
23 | $expected = 'test'.PHP_EOL.'make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
24 | $this->assertEquals($expected, $this->compiler->compileString($string));
25 |
26 | // use the same compiler instance to compile another template with @extends directive
27 | $string = '@extends(name(foo))'.PHP_EOL.'test';
28 | $expected = 'test'.PHP_EOL.'make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
29 | $this->assertEquals($expected, $this->compiler->compileString($string));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/View/ViewCompilerEngineTest.php:
--------------------------------------------------------------------------------
1 | getEngine();
21 | $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php');
22 | $engine->getCompiler()->shouldReceive('isExpired')->once()->with(__DIR__.'/fixtures/foo.php')->andReturn(true);
23 | $engine->getCompiler()->shouldReceive('compile')->once()->with(__DIR__.'/fixtures/foo.php');
24 | $results = $engine->get(__DIR__.'/fixtures/foo.php');
25 |
26 | $this->assertEquals('Hello World
27 | ', $results);
28 | }
29 |
30 | public function testViewsAreNotRecompiledIfTheyAreNotExpired()
31 | {
32 | $engine = $this->getEngine();
33 | $engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__.'/fixtures/foo.php')->andReturn(__DIR__.'/fixtures/basic.php');
34 | $engine->getCompiler()->shouldReceive('isExpired')->once()->andReturn(false);
35 | $engine->getCompiler()->shouldReceive('compile')->never();
36 | $results = $engine->get(__DIR__.'/fixtures/foo.php');
37 |
38 | $this->assertEquals('Hello World
39 | ', $results);
40 | }
41 |
42 | protected function getEngine()
43 | {
44 | return new CompilerEngine(m::mock(CompilerInterface::class));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladePhpStatementsTest.php:
--------------------------------------------------------------------------------
1 | ';
11 | $this->assertEquals($expected, $this->compiler->compileString($string));
12 | }
13 |
14 | public function testPhpStatementsWithoutExpressionAreIgnored()
15 | {
16 | $string = '@php';
17 | $expected = '@php';
18 | $this->assertEquals($expected, $this->compiler->compileString($string));
19 |
20 | $string = '{{ "Ignore: @php" }}';
21 | $expected = '';
22 | $this->assertEquals($expected, $this->compiler->compileString($string));
23 | }
24 |
25 | public function testPhpStatementsDontParseBladeCode()
26 | {
27 | $string = '@php echo "{{ This is a blade tag }}" @endphp';
28 | $expected = '';
29 | $this->assertEquals($expected, $this->compiler->compileString($string));
30 | }
31 |
32 | public function testVerbatimAndPhpStatementsDontGetMixedUp()
33 | {
34 | $string = "@verbatim {{ Hello, I'm not blade! }}"
35 | ."\n@php echo 'And I'm not PHP!' @endphp"
36 | ."\n@endverbatim {{ 'I am Blade' }}"
37 | ."\n@php echo 'I am PHP {{ not Blade }}' @endphp";
38 |
39 | $expected = " {{ Hello, I'm not blade! }}"
40 | ."\n@php echo 'And I'm not PHP!' @endphp"
41 | ."\n "
42 | ."\n\n";
43 |
44 | $this->assertEquals($expected, $this->compiler->compileString($string));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeBreakStatementsTest.php:
--------------------------------------------------------------------------------
1 |
14 | test
15 |
16 | ';
17 | $this->assertEquals($expected, $this->compiler->compileString($string));
18 | }
19 |
20 | public function testBreakStatementsWithExpressionAreCompiled()
21 | {
22 | $string = '@for ($i = 0; $i < 10; $i++)
23 | test
24 | @break(TRUE)
25 | @endfor';
26 | $expected = '
27 | test
28 |
29 | ';
30 | $this->assertEquals($expected, $this->compiler->compileString($string));
31 | }
32 |
33 | public function testBreakStatementsWithArgumentAreCompiled()
34 | {
35 | $string = '@for ($i = 0; $i < 10; $i++)
36 | test
37 | @break(2)
38 | @endfor';
39 | $expected = '
40 | test
41 |
42 | ';
43 | $this->assertEquals($expected, $this->compiler->compileString($string));
44 | }
45 |
46 | public function testBreakStatementsWithSpacedArgumentAreCompiled()
47 | {
48 | $string = '@for ($i = 0; $i < 10; $i++)
49 | test
50 | @break( 2 )
51 | @endfor';
52 | $expected = '
53 | test
54 |
55 | ';
56 | $this->assertEquals($expected, $this->compiler->compileString($string));
57 | }
58 |
59 | public function testBreakStatementsWithFaultyArgumentAreCompiled()
60 | {
61 | $string = '@for ($i = 0; $i < 10; $i++)
62 | test
63 | @break(-2)
64 | @endfor';
65 | $expected = '
66 | test
67 |
68 | ';
69 | $this->assertEquals($expected, $this->compiler->compileString($string));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeContinueStatementsTest.php:
--------------------------------------------------------------------------------
1 |
14 | test
15 |
16 | ';
17 | $this->assertEquals($expected, $this->compiler->compileString($string));
18 | }
19 |
20 | public function testContinueStatementsWithExpressionAreCompiled()
21 | {
22 | $string = '@for ($i = 0; $i < 10; $i++)
23 | test
24 | @continue(TRUE)
25 | @endfor';
26 | $expected = '
27 | test
28 |
29 | ';
30 | $this->assertEquals($expected, $this->compiler->compileString($string));
31 | }
32 |
33 | public function testContinueStatementsWithArgumentAreCompiled()
34 | {
35 | $string = '@for ($i = 0; $i < 10; $i++)
36 | test
37 | @continue(2)
38 | @endfor';
39 | $expected = '
40 | test
41 |
42 | ';
43 | $this->assertEquals($expected, $this->compiler->compileString($string));
44 | }
45 |
46 | public function testContinueStatementsWithSpacedArgumentAreCompiled()
47 | {
48 | $string = '@for ($i = 0; $i < 10; $i++)
49 | test
50 | @continue( 2 )
51 | @endfor';
52 | $expected = '
53 | test
54 |
55 | ';
56 | $this->assertEquals($expected, $this->compiler->compileString($string));
57 | }
58 |
59 | public function testContinueStatementsWithFaultyArgumentAreCompiled()
60 | {
61 | $string = '@for ($i = 0; $i < 10; $i++)
62 | test
63 | @continue(-2)
64 | @endfor';
65 | $expected = '
66 | test
67 |
68 | ';
69 | $this->assertEquals($expected, $this->compiler->compileString($string));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Engines/CompilerEngine.php:
--------------------------------------------------------------------------------
1 | lastCompiled[] = $path;
29 | } else {
30 | $this->lastCompiled[] = 'StringView-'.$path->cache_key;
31 | }
32 |
33 | // If this given view has expired, which means it has simply been edited since
34 | // it was last compiled, we will re-compile the views so we can evaluate a
35 | // fresh copy of the view. We'll pass the compiler the path of the view.
36 | if ($this->compiler->isExpired($path)) {
37 | $this->compiler->compile($path);
38 | }
39 |
40 | $compiled = $this->compiler->getCompiledPath($path);
41 |
42 | // Once we have the path to the compiled file, we will evaluate the paths with
43 | // typical PHP just like any other templates. We also keep a stack of views
44 | // which have been rendered for right exception messages to be generated.
45 | $results = $this->evaluatePath($compiled, $data);
46 |
47 | array_pop($this->lastCompiled);
48 |
49 | return $results;
50 | }
51 |
52 | /**
53 | * Set the delete view cache after render flag.
54 | *
55 | * @deprecated I can't seem to find when the setting was actually used. If you want this, submit a bug and I will see about adding the ability.
56 | *
57 | * @param bool $delete
58 | */
59 | public function setDeleteViewCacheAfterRender($delete = true) {
60 | $this->deleteViewCacheAfterRender = $delete;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeVerbatimTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, $this->compiler->compileString($string));
12 | }
13 |
14 | public function testVerbatimBlocksWithMultipleLinesAreCompiled()
15 | {
16 | $string = 'Some text
17 | @verbatim
18 | {{ $a }}
19 | @if($b)
20 | {{ $b }}
21 | @endif
22 | @endverbatim';
23 | $expected = 'Some text
24 |
25 | {{ $a }}
26 | @if($b)
27 | {{ $b }}
28 | @endif
29 | ';
30 | $this->assertEquals($expected, $this->compiler->compileString($string));
31 | }
32 |
33 | public function testMultipleVerbatimBlocksAreCompiled()
34 | {
35 | $string = '@verbatim {{ $a }} @endverbatim {{ $b }} @verbatim {{ $c }} @endverbatim';
36 | $expected = ' {{ $a }} {{ $c }} ';
37 | $this->assertEquals($expected, $this->compiler->compileString($string));
38 | }
39 |
40 | public function testRawBlocksAreRenderedInTheRightOrder()
41 | {
42 | $string = '@php echo "#1"; @endphp @verbatim {{ #2 }} @endverbatim @verbatim {{ #3 }} @endverbatim @php echo "#4"; @endphp';
43 |
44 | $expected = ' {{ #2 }} {{ #3 }} ';
45 |
46 | $this->assertSame($expected, $this->compiler->compileString($string));
47 | }
48 |
49 | public function testMultilineTemplatesWithRawBlocksAreRenderedInTheRightOrder()
50 | {
51 | $string = '{{ $first }}
52 | @php
53 | echo $second;
54 | @endphp
55 | @if ($conditional)
56 | {{ $third }}
57 | @endif
58 | @include("users")
59 | @verbatim
60 | {{ $fourth }} @include("test")
61 | @endverbatim
62 | @php echo $fifth; @endphp';
63 |
64 | $expected = '
65 |
66 |
69 |
70 |
71 |
72 |
73 | make("users", \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>
74 |
75 | {{ $fourth }} @include("test")
76 |
77 | ';
78 |
79 | $this->assertSame($expected, $this->compiler->compileString($string));
80 | }
81 |
82 | public function testRawBlocksDontGetMixedUpWhenSomeAreRemovedByBladeComments()
83 | {
84 | $string = '{{-- @verbatim Block #1 @endverbatim --}} @php "Block #2" @endphp';
85 | $expected = ' ';
86 |
87 | $this->assertSame($expected, $this->compiler->compileString($string));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeForelseStatementsTest.php:
--------------------------------------------------------------------------------
1 | getUsers() as $user)
10 | breeze
11 | @empty
12 | empty
13 | @endforelse';
14 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
15 | breeze
16 | popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
17 | empty
18 | ';
19 | $this->assertEquals($expected, $this->compiler->compileString($string));
20 | }
21 |
22 | public function testForelseStatementsAreCompiledWithUppercaseSyntax()
23 | {
24 | $string = '@forelse ($this->getUsers() AS $user)
25 | breeze
26 | @empty
27 | empty
28 | @endforelse';
29 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
30 | breeze
31 | popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
32 | empty
33 | ';
34 | $this->assertEquals($expected, $this->compiler->compileString($string));
35 | }
36 |
37 | public function testForelseStatementsAreCompiledWithMultipleLine()
38 | {
39 | $string = '@forelse ([
40 | foo,
41 | bar,
42 | ] as $label)
43 | breeze
44 | @empty
45 | empty
46 | @endforelse';
47 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
51 | breeze
52 | popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
53 | empty
54 | ';
55 | $this->assertEquals($expected, $this->compiler->compileString($string));
56 | }
57 |
58 | public function testNestedForelseStatementsAreCompiled()
59 | {
60 | $string = '@forelse ($this->getUsers() as $user)
61 | @forelse ($user->tags as $tag)
62 | breeze
63 | @empty
64 | tag empty
65 | @endforelse
66 | @empty
67 | empty
68 | @endforelse';
69 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
70 | tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_2 = false; ?>
71 | breeze
72 | popLoop(); $loop = $__env->getLastLoop(); if ($__empty_2): ?>
73 | tag empty
74 |
75 | popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
76 | empty
77 | ';
78 | $this->assertEquals($expected, $this->compiler->compileString($string));
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Compilers/BladeCompiler.php:
--------------------------------------------------------------------------------
1 | rawTags = [preg_quote($openTag), preg_quote($closeTag)];
58 | }
59 |
60 | /**
61 | * Sets the content tags used for the compiler.
62 | *
63 | * @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736)
64 | *
65 | * @param string $openTag
66 | * @param string $closeTag
67 | * @param bool $escaped
68 | * @return void
69 | */
70 | public function setContentTags($openTag, $closeTag, $escaped = true)
71 | {
72 | $this->contentTags = [preg_quote($openTag), preg_quote($closeTag)];
73 | }
74 |
75 | /**
76 | * Sets the escape tags used for the compiler.
77 | *
78 | * @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736)
79 | *
80 | * @param string $openTag
81 | * @param string $closeTag
82 | * @param bool $escaped
83 | * @return void
84 | */
85 | public function setEscapeTags($openTag, $closeTag, $escaped = true)
86 | {
87 | $this->escapedTags = [preg_quote($openTag), preg_quote($closeTag)];
88 | }
89 |
90 | /**
91 | * Enable/Disable force recompile of templates.
92 | *
93 | * @param bool $recompile
94 | * @return void
95 | */
96 | public function setForceTemplateRecompile($recompile = true) {
97 | $this->forceTemplateRecompile = $recompile;
98 | }
99 |
100 | /**
101 | * Determine if the view at the given path is expired.
102 | *
103 | * @param string $path
104 | * @return bool
105 | */
106 | public function isExpired($path)
107 | {
108 |
109 | // adds ability to force template recompile
110 | if ($this->forceTemplateRecompile) {
111 | return true;
112 | }
113 |
114 | return parent::isExpired($path);
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeEchoTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('', $this->compiler->compileString('{!!$name!!}'));
10 | $this->assertEquals('', $this->compiler->compileString('{!! $name !!}'));
11 | $this->assertEquals('', $this->compiler->compileString('{!!
12 | $name
13 | !!}'));
14 |
15 | $this->assertEquals('', $this->compiler->compileString('{{{$name}}}'));
16 | $this->assertEquals('', $this->compiler->compileString('{{$name}}'));
17 | $this->assertEquals('', $this->compiler->compileString('{{ $name }}'));
18 | $this->assertEquals('', $this->compiler->compileString('{{
19 | $name
20 | }}'));
21 | $this->assertEquals("\n\n", $this->compiler->compileString("{{ \$name }}\n"));
22 | $this->assertEquals("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n"));
23 | $this->assertEquals("\n\n", $this->compiler->compileString("{{ \$name }}\n"));
24 | $this->assertEquals("\r\n\r\n", $this->compiler->compileString("{{ \$name }}\r\n"));
25 |
26 | $this->assertEquals('',
27 | $this->compiler->compileString('{{ "Hello world or foo" }}'));
28 | $this->assertEquals('',
29 | $this->compiler->compileString('{{"Hello world or foo"}}'));
30 | $this->assertEquals('', $this->compiler->compileString('{{$foo + $or + $baz}}'));
31 | $this->assertEquals('', $this->compiler->compileString('{{
32 | "Hello world or foo"
33 | }}'));
34 |
35 | $this->assertEquals('',
36 | $this->compiler->compileString('{{ \'Hello world or foo\' }}'));
37 | $this->assertEquals('',
38 | $this->compiler->compileString('{{\'Hello world or foo\'}}'));
39 | $this->assertEquals('', $this->compiler->compileString('{{
40 | \'Hello world or foo\'
41 | }}'));
42 |
43 | $this->assertEquals('',
44 | $this->compiler->compileString('{{ myfunc(\'foo or bar\') }}'));
45 | $this->assertEquals('',
46 | $this->compiler->compileString('{{ myfunc("foo or bar") }}'));
47 | $this->assertEquals('',
48 | $this->compiler->compileString('{{ myfunc("$name or \'foo\'") }}'));
49 | }
50 |
51 | public function testEscapedWithAtEchosAreCompiled()
52 | {
53 | $this->assertEquals('{{$name}}', $this->compiler->compileString('@{{$name}}'));
54 | $this->assertEquals('{{ $name }}', $this->compiler->compileString('@{{ $name }}'));
55 | $this->assertEquals('{{
56 | $name
57 | }}',
58 | $this->compiler->compileString('@{{
59 | $name
60 | }}'));
61 | $this->assertEquals('{{ $name }}
62 | ',
63 | $this->compiler->compileString('@{{ $name }}
64 | '));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/StringView.php:
--------------------------------------------------------------------------------
1 | view = (is_array($view))?(object) $view:$view;
33 | $this->path = $path;
34 | $this->engine = $engine;
35 | $this->factory = $factory;
36 |
37 | $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data;
38 |
39 | // check if view has secondsTemplateCacheExpires set, or get from config
40 | if ( !property_exists($this->view, "secondsTemplateCacheExpires") || !is_numeric($this->view->secondsTemplateCacheExpires) ) {
41 | $this->view->secondsTemplateCacheExpires = config('blade.secondsTemplateCacheExpires');
42 | if ( is_null($this->view->secondsTemplateCacheExpires) ) {
43 | $this->view->secondsTemplateCacheExpires = 0;
44 | }
45 | }
46 |
47 | // this is the actually blade template data
48 | if ( !property_exists($this->view, "template") )
49 | {
50 | // is the same as sending a blank template file
51 | $this->view->template = '';
52 | }
53 |
54 | // each template requires a unique cache key, or generate one
55 | if ( !property_exists($this->view, "cache_key") )
56 | {
57 | // special, to catch if template is empty
58 | if (empty($this->view->template)) {
59 | $this->view->cache_key = md5('_empty_template_');
60 | } else {
61 | $this->view->cache_key = md5($this->view->template);
62 | }
63 | }
64 | }
65 |
66 | public function getViewTemplate() {
67 | return $this->view->template;
68 | }
69 |
70 | /**
71 | * Get the name of the view.
72 | *
73 | * @return string
74 | */
75 | public function getName()
76 | {
77 | return (isset($this->view->template)?md5($this->view->template):'StringViewTemplate');
78 | }
79 |
80 | /**
81 | * Get the evaluated contents of the view.
82 | *
83 | * @return string
84 | */
85 | protected function getContents()
86 | {
87 |
88 |
89 | /**
90 | * This property will be added to models being compiled with StringView
91 | * to keep track of which field in the model is being compiled
92 | */
93 | //$this->path->__string_blade_compiler_template_field = $this->template_field;
94 |
95 | if (is_null($this->path)) {
96 | return $this->engine->get($this->view, $this->gatherData());
97 | }
98 |
99 | return $this->engine->get($this->path, $this->gatherData());
100 | //return parent::getContents();
101 | }
102 |
103 | /**
104 | * Checks if a string is a valid timestamp.
105 | * from https://gist.github.com/sepehr/6351385
106 | *
107 | * @param string $timestamp Timestamp to validate.
108 | *
109 | * @return bool
110 | */
111 | function is_timestamp($timestamp)
112 | {
113 | $check = (is_int($timestamp) OR is_float($timestamp))
114 | ? $timestamp
115 | : (string) (int) $timestamp;
116 |
117 | return ($check === $timestamp)
118 | AND ( (int) $timestamp <= PHP_INT_MAX)
119 | AND ( (int) $timestamp >= ~PHP_INT_MAX);
120 | }
121 |
122 |
123 | }
--------------------------------------------------------------------------------
/tests/View/Blade/BladeForeachStatementsTest.php:
--------------------------------------------------------------------------------
1 | getUsers() as $user)
10 | test
11 | @endforeach';
12 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
13 | test
14 | popLoop(); $loop = $__env->getLastLoop(); ?>';
15 | $this->assertEquals($expected, $this->compiler->compileString($string));
16 | }
17 |
18 | public function testForeachStatementsAreCompileWithUppercaseSyntax()
19 | {
20 | $string = '@foreach ($this->getUsers() AS $user)
21 | test
22 | @endforeach';
23 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
24 | test
25 | popLoop(); $loop = $__env->getLastLoop(); ?>';
26 | $this->assertEquals($expected, $this->compiler->compileString($string));
27 | }
28 |
29 | public function testForeachStatementsAreCompileWithMultipleLine()
30 | {
31 | $string = '@foreach ([
32 | foo,
33 | bar,
34 | ] as $label)
35 | test
36 | @endforeach';
37 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
41 | test
42 | popLoop(); $loop = $__env->getLastLoop(); ?>';
43 | $this->assertEquals($expected, $this->compiler->compileString($string));
44 | }
45 |
46 | public function testNestedForeachStatementsAreCompiled()
47 | {
48 | $string = '@foreach ($this->getUsers() as $user)
49 | user info
50 | @foreach ($user->tags as $tag)
51 | tag info
52 | @endforeach
53 | @endforeach';
54 | $expected = 'getUsers(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
55 | user info
56 | tags; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $tag): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
57 | tag info
58 | popLoop(); $loop = $__env->getLastLoop(); ?>
59 | popLoop(); $loop = $__env->getLastLoop(); ?>';
60 | $this->assertEquals($expected, $this->compiler->compileString($string));
61 | }
62 |
63 | public function testLoopContentHolderIsExtractedFromForeachStatements()
64 | {
65 | $string = '@foreach ($some_uSers1 as $user)';
66 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>';
67 | $this->assertEquals($expected, $this->compiler->compileString($string));
68 |
69 | $string = '@foreach ($users->get() as $user)';
70 | $expected = 'get(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>';
71 | $this->assertEquals($expected, $this->compiler->compileString($string));
72 |
73 | $string = '@foreach (range(1, 4) as $user)';
74 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>';
75 | $this->assertEquals($expected, $this->compiler->compileString($string));
76 |
77 | $string = '@foreach ( $users as $user)';
78 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $user): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>';
79 | $this->assertEquals($expected, $this->compiler->compileString($string));
80 |
81 | $string = '@foreach ($tasks as $task)';
82 | $expected = 'addLoop($__currentLoopData); foreach($__currentLoopData as $task): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>';
83 | $this->assertEquals($expected, $this->compiler->compileString($string));
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Compilers/StringBladeCompiler.php:
--------------------------------------------------------------------------------
1 | files = $files;
28 | $this->cachePath = $cachePath;
29 | }
30 |
31 | public function setViewData($viewData) {
32 | $this->viewData = $viewData;
33 | }
34 |
35 | /**
36 | * Compile the view at the given path.
37 | *
38 | * @param object $viewData
39 | * @return void
40 | */
41 | public function compile($viewData = null)
42 | {
43 | if (!is_null($viewData)) {
44 | $this->viewData = $viewData;
45 | }
46 |
47 | if (property_exists($this->viewData, 'cache_key'))
48 | {
49 | $this->setPath($this->viewData->cache_key);
50 | }
51 |
52 | $contents = $this->compileString(
53 | $this->viewData->template
54 | );
55 |
56 | $tokens = $this->getOpenAndClosingPhpTokens($contents);
57 |
58 | // If the tokens we retrieved from the compiled contents have at least
59 | // one opening tag and if that last token isn't the closing tag, we
60 | // need to close the statement before adding the path at the end.
61 | if ($tokens->isNotEmpty() && $tokens->last() !== T_CLOSE_TAG) {
62 | $contents .= ' ?>';
63 | }
64 |
65 | if (isset($this->viewData->templateRefKey)) {
66 | $contents .= "viewData->templateRefKey} ENDPATH**/ ?>";
67 | }
68 |
69 | if (! is_null($this->cachePath)) {
70 | $this->files->put(
71 | $this->getCompiledPath($this->viewData), $contents
72 | );
73 | }
74 | }
75 |
76 | /**
77 | * Get the path to the compiled version of a view.
78 | *
79 | * @param string $path
80 | * @return string
81 | */
82 | public function getCompiledPath($viewData)
83 | {
84 | if (!property_exists($viewData, 'cache_key'))
85 | {
86 | $cacheKey = Str::random(40);
87 | while (in_array($cacheKey, $this->use_cache_keys)) {
88 | $cacheKey = Str::random(40);
89 | }
90 |
91 | $viewData->cache_key = $cacheKey;
92 | }
93 |
94 | return $this->cachePath.'/'.sha1($viewData->cache_key).'.php';
95 | }
96 |
97 | /**
98 | * Determine if the view at the given path is expired.
99 | *
100 | * @param object $viewData
101 | * @return bool
102 | */
103 | public function isExpired($viewData)
104 | {
105 |
106 | // adds ability to force template recompile
107 | if ($this->forceTemplateRecompile) {
108 | return true;
109 | }
110 |
111 | $compiled = $this->getCompiledPath($viewData);
112 |
113 | // If the compiled file doesn't exist we will indicate that the view is expired
114 | // so that it can be re-compiled. Else, we will verify the last modification
115 | // of the views is less than the modification times of the compiled views.
116 | if ( ! $this->cachePath || ! $this->files->exists($compiled))
117 | {
118 | return true;
119 | }
120 |
121 | // If set to 0, then return cache has expired
122 | if (property_exists($viewData, 'secondsTemplateCacheExpires')) {
123 | if ($viewData->secondsTemplateCacheExpires == 0) {
124 | return true;
125 | }
126 | } else {
127 | $viewData->secondsTemplateCacheExpires = 0;
128 | }
129 |
130 | // Note: The lastModified time for a file on homestead will use the time from the host system.
131 | // This means the vm time could be off, so setting the timeout to seconds may not work as expected.
132 |
133 | return time() >= ($this->files->lastModified($compiled) + $viewData->secondsTemplateCacheExpires) ;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 | 'blade',
55 | 'php' => 'php',
56 | 'css' => 'file',
57 | ];
58 |
59 | /**
60 | * The view composer events.
61 | *
62 | * @var array
63 | */
64 | protected $composers = [];
65 |
66 | /**
67 | * The number of active rendering operations.
68 | *
69 | * @var int
70 | */
71 | protected $renderCount = 0;
72 |
73 | /**
74 | * Get the evaluated view contents for the given view.
75 | *
76 | * @param string|array $view
77 | * @param array $data
78 | * @param array $mergeData
79 | * @return \Illuminate\Contracts\View\View|\Wpb\String_Blade_Compiler\StringView
80 | */
81 | public function make($view, $data = [], $mergeData = [])
82 | {
83 | $data = array_merge($mergeData, $this->parseData($data));
84 |
85 | // For string rendering
86 | if (is_array($view)) {
87 | return tap($this->stringViewInstance($view, $data), function ($view) {
88 | $this->callCreator($view);
89 | });
90 | }
91 |
92 | $path = $this->finder->find(
93 | $view = $this->normalizeName($view)
94 | );
95 |
96 | // Next, we will create the view instance and call the view creator for the view
97 | // which can set any data, etc. Then we will return the view instance back to
98 | // the caller for rendering or performing other view manipulations on this.
99 |
100 | return tap($this->viewInstance($view, $path, $data), function ($view) {
101 | $this->callCreator($view);
102 | });
103 | }
104 |
105 | /**
106 | * Create a new string view instance from the given arguments.
107 | *
108 | * @param string|array $view
109 | * @param array $data
110 | * @return StringView
111 | */
112 | protected function stringViewInstance($view, $data)
113 | {
114 | return new StringView($this, $this->engines->resolve('stringblade'), $view, null, $data);
115 | }
116 |
117 | /**
118 | * Flush all of the section contents if done rendering.
119 | *
120 | * @return void
121 | */
122 | public function flushStateIfDoneRendering()
123 | {
124 | if ($this->doneRendering()) {
125 | $this->flushState();
126 | }
127 | }
128 |
129 | /**
130 | * Flush all of the factory state like sections and stacks.
131 | *
132 | * @return void
133 | */
134 | public function flushState()
135 | {
136 | $this->renderCount = 0;
137 |
138 | $this->flushSections();
139 | $this->flushStacks();
140 | }
141 |
142 | /**
143 | * Get the appropriate view engine for the given string key.
144 | *
145 | * @param string $stringkey
146 | * @return \Illuminate\Contracts\View\Engine
147 | *
148 | * ['file', 'php', 'blade', 'stringblade'] in StringBladeServiceProvider:registerEngineResolver
149 | *
150 | * @throws \InvalidArgumentException
151 | */
152 | public function getEngineFromStringKey($stringkey)
153 | {
154 | // resolve function throws error if $stringkey is not a registered engine
155 | return $this->engines->resolve($stringkey);
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/src/StringBladeServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom(
23 | __DIR__ . '/../config/blade.php', 'blade'
24 | );
25 |
26 | $this->registerFactory();
27 | $this->registerViewFinder();
28 | $this->registerBladeCompiler();
29 | $this->registerStringbladeCompiler();
30 | $this->registerEngineResolver();
31 |
32 | }
33 |
34 | /**
35 | * Register the view environment.
36 | *
37 | * @return void
38 | */
39 | public function registerFactory()
40 | {
41 | $this->app->singleton('view', function ($app) {
42 | // Next we need to grab the engine resolver instance that will be used by the
43 | // environment. The resolver will be used by an environment to get each of
44 | // the various engine implementations such as plain PHP or Blade engine.
45 | $resolver = $app['view.engine.resolver'];
46 |
47 | $finder = $app['view.finder'];
48 |
49 | $factory = $this->createFactory($resolver, $finder, $app['events']);
50 |
51 | // We will also set the container instance on this view environment since the
52 | // view composers may be classes registered in the container, which allows
53 | // for great testable, flexible composers for the application developer.
54 | $factory->setContainer($app);
55 |
56 | $factory->share('app', $app);
57 |
58 | return $factory;
59 | });
60 | }
61 |
62 | /**
63 | * Create a new Factory Instance.
64 | *
65 | * @param \Illuminate\View\Engines\EngineResolver $resolver
66 | * @param \Illuminate\View\ViewFinderInterface $finder
67 | * @param \Illuminate\Contracts\Events\Dispatcher $events
68 | * @return \Illuminate\View\Factory
69 | */
70 | protected function createFactory($resolver, $finder, $events)
71 | {
72 | return new Factory($resolver, $finder, $events);
73 | }
74 |
75 | /**
76 | * Register the view finder implementation.
77 | *
78 | * @return void
79 | */
80 | public function registerViewFinder()
81 | {
82 | // since view.find should be registered, lets get the paths and hints - in case they have changed
83 | $oldFinder = [];
84 | if ($this->app->resolved('view.finder')) {
85 | $oldFinder['paths'] = $this->app['view']->getFinder()->getPaths();
86 | $oldFinder['hints'] = $this->app['view']->getFinder()->getHints();
87 | }
88 |
89 | // recreate the view.finder
90 | $this->app->bind('view.finder', function ($app) use ($oldFinder) {
91 |
92 | $paths = (isset($oldFinder['paths'])) ? array_unique(array_merge($app['config']['view.paths'], $oldFinder['paths']), SORT_REGULAR) : $app['config']['view.paths'];
93 |
94 | $viewFinder = new FileViewFinder($app['files'], $paths);
95 |
96 | if (!empty($oldFinder['hints'])) {
97 | array_walk($oldFinder['hints'], function ($value, $key) use ($viewFinder) {
98 | $viewFinder->addNamespace($key, $value);
99 | });
100 | }
101 |
102 | return $viewFinder;
103 | });
104 | }
105 |
106 | /**
107 | * Register the engine resolver instance.
108 | *
109 | * @return void
110 | */
111 | public function registerEngineResolver()
112 | {
113 | // recreate the resolver, adding stringblade
114 | $this->app->singleton('view.engine.resolver', function ($app) {
115 | $resolver = new EngineResolver;
116 |
117 | // Next, we will register the various view engines with the resolver so that the
118 | // environment will resolve the engines needed for various views based on the
119 | // extension of view file. We call a method for each of the view's engines.
120 | foreach (['file', 'php', 'blade', 'stringblade'] as $engine) {
121 | $this->{'register' . ucfirst($engine) . 'Engine'}($resolver);
122 | }
123 |
124 | return $resolver;
125 | });
126 | }
127 |
128 | /**
129 | * Register the String Blade compiler implementation.
130 | *
131 | * @return void
132 | */
133 | public function registerStringbladeCompiler()
134 | {
135 | // The Compiler engine requires an instance of the CompilerInterface, which in
136 | // this case will be the Blade compiler, so we'll first create the compiler
137 | // instance to pass into the engine so it can compile the views properly.
138 | $this->app->singleton('stringblade.compiler', function () {
139 | $cache = $this->app['config']['view.compiled'];
140 | return new StringBladeCompiler($this->app['files'], $cache);
141 | });
142 | }
143 |
144 | /**
145 | * Register the StringBlade engine implementation.
146 | *
147 | * @param \Illuminate\View\Engines\EngineResolver $resolver
148 | * @return void
149 | */
150 | public function registerStringbladeEngine($resolver)
151 | {
152 | $resolver->register('stringblade', function () {
153 | return new CompilerEngine($this->app['stringblade.compiler']);
154 | });
155 | }
156 |
157 | /**
158 | * Bootstrap any application services.
159 | *
160 | * @return void
161 | */
162 | public function boot()
163 | {
164 | if (config('blade.autoload_custom_directives')) {
165 | $blade = app('blade.compiler');
166 | $string_blade = app('stringblade.compiler');
167 |
168 | collect($blade->getCustomDirectives())
169 | ->each(function ($directive, $name) use ($string_blade) {
170 | $string_blade->directive($name, $directive);
171 | });
172 | }
173 | }
174 |
175 | /**
176 | * Get the services provided by the provider.
177 | *
178 | * @return array
179 | */
180 | public function provides()
181 | {
182 | return [
183 | StringBlade::class,
184 | 'view',
185 | ViewFactory::class,
186 | ];
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/tests/View/Blade/BladeCustomTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(' ', $this->compiler->compileString("@if(\$test) @endif"));
10 | }
11 |
12 | public function testMixingYieldAndEcho()
13 | {
14 | $this->assertEquals('yieldContent(\'title\'); ?> - ', $this->compiler->compileString("@yield('title') - {{Config::get('site.title')}}"));
15 | }
16 |
17 | public function testCustomExtensionsAreCompiled()
18 | {
19 | $this->compiler->extend(function ($value) {
20 | return str_replace('foo', 'bar', $value);
21 | });
22 | $this->assertEquals('bar', $this->compiler->compileString('foo'));
23 | }
24 |
25 | public function testCustomStatements()
26 | {
27 | $this->assertCount(0, $this->compiler->getCustomDirectives());
28 | $this->compiler->directive('customControl', function ($expression) {
29 | return "";
30 | });
31 | $this->assertCount(1, $this->compiler->getCustomDirectives());
32 |
33 | $string = '@if($foo)
34 | @customControl(10, $foo, \'bar\')
35 | @endif';
36 | $expected = '
37 |
38 | ';
39 | $this->assertEquals($expected, $this->compiler->compileString($string));
40 | }
41 |
42 | public function testCustomShortStatements()
43 | {
44 | $this->compiler->directive('customControl', function ($expression) {
45 | return '';
46 | });
47 |
48 | $string = '@customControl';
49 | $expected = '';
50 | $this->assertEquals($expected, $this->compiler->compileString($string));
51 | }
52 |
53 | public function testValidCustomNames()
54 | {
55 | $this->assertNull($this->compiler->directive('custom', function () {
56 | }));
57 | $this->assertNull($this->compiler->directive('custom_custom', function () {
58 | }));
59 | $this->assertNull($this->compiler->directive('customCustom', function () {
60 | }));
61 | $this->assertNull($this->compiler->directive('custom::custom', function () {
62 | }));
63 | }
64 |
65 | public function testInvalidCustomNames()
66 | {
67 | $this->expectException(\InvalidArgumentException::class);
68 | $this->expectExceptionMessage('The directive name [custom-custom] is not valid.');
69 | $this->compiler->directive('custom-custom', function () {
70 | });
71 | }
72 |
73 | public function testInvalidCustomNames2()
74 | {
75 | $this->expectException(\InvalidArgumentException::class);
76 | $this->expectExceptionMessage('The directive name [custom:custom] is not valid.');
77 | $this->compiler->directive('custom:custom', function () {
78 | });
79 | }
80 |
81 | public function testCustomExtensionOverwritesCore()
82 | {
83 | $this->compiler->directive('foreach', function ($expression) {
84 | return '';
85 | });
86 |
87 | $string = '@foreach';
88 | $expected = '';
89 | $this->assertEquals($expected, $this->compiler->compileString($string));
90 | }
91 |
92 | public function testCustomConditions()
93 | {
94 | $this->compiler->if('custom', function ($user) {
95 | return true;
96 | });
97 |
98 | $string = '@custom($user)
99 | @endcustom';
100 | $expected = '
101 | ';
102 | $this->assertEquals($expected, $this->compiler->compileString($string));
103 | }
104 |
105 | public function testCustomIfElseConditions()
106 | {
107 | $this->compiler->if('custom', function ($anything) {
108 | return true;
109 | });
110 |
111 | $string = '@custom($user)
112 | @elsecustom($product)
113 | @else
114 | @endcustom';
115 | $expected = '
116 |
117 |
118 | ';
119 | $this->assertEquals($expected, $this->compiler->compileString($string));
120 | }
121 |
122 | public function testCustomConditionsAccepts0AsArgument()
123 | {
124 | $this->compiler->if('custom', function ($number) {
125 | return true;
126 | });
127 |
128 | $string = '@custom(0)
129 | @elsecustom(0)
130 | @endcustom';
131 | $expected = '
132 |
133 | ';
134 | $this->assertEquals($expected, $this->compiler->compileString($string));
135 | }
136 |
137 | public function testCustomComponents()
138 | {
139 | $this->compiler->component('app.components.alert', 'alert');
140 |
141 | $string = '@alert
142 | @endalert';
143 | $expected = 'startComponent(\'app.components.alert\'); ?>
144 | renderComponent(); ?>';
145 | $this->assertEquals($expected, $this->compiler->compileString($string));
146 | }
147 |
148 | public function testCustomComponentsWithSlots()
149 | {
150 | $this->compiler->component('app.components.alert', 'alert');
151 |
152 | $string = '@alert([\'type\' => \'danger\'])
153 | @endalert';
154 | $expected = 'startComponent(\'app.components.alert\', [\'type\' => \'danger\']); ?>
155 | renderComponent(); ?>';
156 | $this->assertEquals($expected, $this->compiler->compileString($string));
157 | }
158 |
159 | public function testCustomComponentsDefaultAlias()
160 | {
161 | $this->compiler->component('app.components.alert');
162 |
163 | $string = '@alert
164 | @endalert';
165 | $expected = 'startComponent(\'app.components.alert\'); ?>
166 | renderComponent(); ?>';
167 | $this->assertEquals($expected, $this->compiler->compileString($string));
168 | }
169 |
170 | public function testCustomComponentsWithExistingDirective()
171 | {
172 | $this->compiler->component('app.components.foreach');
173 |
174 | $string = '@foreach
175 | @endforeach';
176 | $expected = 'startComponent(\'app.components.foreach\'); ?>
177 | renderComponent(); ?>';
178 | $this->assertEquals($expected, $this->compiler->compileString($string));
179 | }
180 |
181 | public function testCustomIncludes()
182 | {
183 | $this->compiler->include('app.includes.input', 'input');
184 |
185 | $string = '@input';
186 | $expected = 'make(\'app.includes.input\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
187 | $this->assertEquals($expected, $this->compiler->compileString($string));
188 | }
189 |
190 | public function testCustomIncludesWithData()
191 | {
192 | $this->compiler->include('app.includes.input', 'input');
193 |
194 | $string = '@input([\'type\' => \'email\'])';
195 | $expected = 'make(\'app.includes.input\', [\'type\' => \'email\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
196 | $this->assertEquals($expected, $this->compiler->compileString($string));
197 | }
198 |
199 | public function testCustomIncludesDefaultAlias()
200 | {
201 | $this->compiler->include('app.includes.input');
202 |
203 | $string = '@input';
204 | $expected = 'make(\'app.includes.input\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
205 | $this->assertEquals($expected, $this->compiler->compileString($string));
206 | }
207 |
208 | public function testCustomIncludesWithExistingDirective()
209 | {
210 | $this->compiler->include('app.includes.foreach');
211 |
212 | $string = '@foreach';
213 | $expected = 'make(\'app.includes.foreach\', [], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>';
214 | $this->assertEquals($expected, $this->compiler->compileString($string));
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/tests/View/ViewStringBladeCompilerTest.php:
--------------------------------------------------------------------------------
1 | getFiles(), __DIR__);
21 | $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(false);
22 | $this->assertTrue($compiler->isExpired((object)['cache_key' => 'foo']));
23 | }
24 |
25 | public function testCannotConstructWithBadCachePath()
26 | {
27 | /* StringBladeComplier can have a empty cache path */
28 |
29 | //$this->expectException(InvalidArgumentException::class);
30 | //$this->expectExceptionMessage('Please provide a valid cache path.');
31 |
32 | //new BladeCompiler($this->getFiles(), null);
33 | $this->assertTrue(true);
34 | }
35 |
36 | public function testIsExpiredReturnsTrueWhenModificationTimesWarrant()
37 | {
38 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
39 | $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(true);
40 | //$files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100);
41 | $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(0);
42 |
43 | $this->assertTrue($compiler->isExpired((object)['cache_key' => 'foo']));
44 | }
45 |
46 | public function testCompilePathIsProperlyCreated()
47 | {
48 | $compiler = new BladeCompiler($this->getFiles(), __DIR__);
49 | $this->assertEquals(__DIR__.'/'.sha1('foo').'.php', $compiler->getCompiledPath((object)['cache_key' => 'foo']));
50 | }
51 |
52 | public function testCompileCompilesFileAndReturnsContents()
53 | {
54 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
55 | //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
56 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World');
57 | $compiler->compile((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']);
58 | }
59 |
60 | public function testCompileCompilesAndGetThePath()
61 | {
62 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
63 | //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
64 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World');
65 | $compiler->compile((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']);
66 | $this->assertEquals('foo', $compiler->getPath());
67 | }
68 |
69 | public function testCompileSetAndGetThePath()
70 | {
71 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
72 | $compiler->setPath('foo');
73 | $this->assertEquals('foo', $compiler->getPath());
74 | }
75 |
76 | public function testCompileWithPathSetBefore()
77 | {
78 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
79 | //$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
80 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World');
81 | // set path before compilation
82 | $compiler->setViewData((object)['template' => 'Hello World', 'templateRefKey' => 'foo', 'cache_key' => 'foo']);
83 | // trigger compilation with $path
84 | $compiler->compile();
85 | $this->assertEquals('foo', $compiler->getPath());
86 | }
87 |
88 | public function testRawTagsCanBeSetToLegacyValues()
89 | {
90 | $compiler = new BladeCompiler($this->getFiles(), __DIR__);
91 | $compiler->setEchoFormat('%s');
92 |
93 | $this->assertEquals('', $compiler->compileString('{{{ $name }}}'));
94 | $this->assertEquals('', $compiler->compileString('{{ $name }}'));
95 | $this->assertEquals('', $compiler->compileString('{{
96 | $name
97 | }}'));
98 | }
99 |
100 | /**
101 | * @param string $content
102 | * @param string $compiled
103 | *
104 | * @dataProvider appendViewPathDataProvider
105 | */
106 | public function testIncludePathToTemplate($content, $compiled)
107 | {
108 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
109 | //$files->shouldReceive('get')->once()->with('foo')->andReturn($content);
110 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', $compiled);
111 |
112 | $compiler->compile((object)['template' => $content, 'templateRefKey' => 'foo', 'cache_key' => 'foo']);
113 | }
114 |
115 | /**
116 | * @return array
117 | */
118 | public function appendViewPathDataProvider()
119 | {
120 | return [
121 | 'No PHP blocks' => [
122 | 'Hello World',
123 | 'Hello World',
124 | ],
125 | 'Single PHP block without closing ?>' => [
126 | '',
128 | ],
129 | 'Ending PHP block.' => [
130 | 'Hello world',
131 | 'Hello world',
132 | ],
133 | 'Ending PHP block without closing ?>' => [
134 | 'Hello world',
136 | ],
137 | 'PHP block between content.' => [
138 | 'Hello worldHi There',
139 | 'Hello worldHi There',
140 | ],
141 | 'Multiple PHP blocks.' => [
142 | 'Hello worldHi ThereHello Again',
143 | 'Hello worldHi ThereHello Again',
144 | ],
145 | 'Multiple PHP blocks without closing ?>' => [
146 | 'Hello worldHi ThereHi There',
148 | ],
149 | 'Short open echo tag' => [
150 | 'Hello world= echo $path',
151 | 'Hello world= echo $path ?>',
152 | ],
153 | 'Echo XML declaration' => [
154 | '\';',
155 | '\'; ?>',
156 | ],
157 | ];
158 | }
159 |
160 | public function testDontIncludeEmptyPath()
161 | {
162 | /* Stupid test, you can't get a file without a file name */
163 | /*
164 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
165 | $files->shouldReceive('get')->once()->with('')->andReturn('Hello World');
166 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('').'.php', 'Hello World');
167 | $compiler->setPath('');
168 | $compiler->compile();
169 | */
170 | }
171 |
172 | public function testDontIncludeNullPath()
173 | {
174 | /* Stupid test, you can't get a file named null */
175 | /*
176 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
177 | $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World');
178 | $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1(null).'.php', 'Hello World');
179 | $compiler->setPath(null);
180 | $compiler->compile();
181 | */
182 | }
183 |
184 | public function testShouldStartFromStrictTypesDeclaration()
185 | {
186 | $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
187 | $strictTypeDecl = "assertTrue(substr($compiler->compileString(" This is a direct extension of \Illuminate\View\View and is build to replace its usage. It will replace the default View instance.
11 |
12 | Versions
13 | =======================
14 | | String Blade | Laravel Version |
15 | | ------------- |----------------:|
16 | | 6.0 | Laravel 8 |
17 | | 5.0 | Laravel 7 |
18 | | 4.0 | Laravel 6 |
19 | | 3.8 | Laravel 5.8 |
20 | | 3.7 | Laravel 5.7 |
21 | | 3.6 | Laravel 5.6 |
22 | | 3.5 | Laravel 5.5 |
23 | | 3.4 | Laravel 5.4 |
24 | | 3.3 | Laravel 5.2 |
25 | | 3.2 | Laravel 5.1 |
26 | | 2.* | Laravel 5 |
27 | | 1.* | Laravel 4.2 |
28 |
29 | Version 3.8 : Updates
30 | =======================
31 | > The package has been completely rewritten, all code updated to be more in line with the Laravel version code. Several of the functions in the extended class were not needed and have been removed and the code has been cleaned up a bit.
32 |
33 | > Also updated the tests to what is available in Laravel View Tests. Some are not applicable to StringBlade.
34 |
35 | Changes,
36 | - Now uses Laravel auto registration feature.
37 | - No long need to remove ```Illuminate\View\ViewServiceProvider::class``` from ```config/app.php```
38 | - The ability to ```setRawTags```, ```setContentTags```, and ```setEscapedTags``` have been removed from Laravel (https://github.com/laravel/framework/issues/17736). They are depreciated here.
39 | - The compiler function ```setDeleteViewCacheAfterRender```, has been deprecated as I didn't find any code where it was actually being used.
40 | - Added more tests
41 |
42 | Installation
43 | =======================
44 |
45 | Add the package to composer.json:
46 |
47 | "require": {
48 | ...
49 | "wpb/string-blade-compiler": "VERSION"
50 | },
51 |
52 | > To get versions 'composer show wpb/string-blade-compiler', such as 'dev-master, * 3.2.x-dev, 3.2.0, 3.0.x-dev, 3.0.0, 2.1.0, 2.0.x-dev, 2.0.0, 1.0.x-dev, 1.0.0'
53 |
54 | On packagist.org at https://packagist.org/packages/wpb/string-blade-compiler
55 |
56 | composer require "wpb/string-blade-compiler"
57 |
58 | Configuration
59 | =======================
60 |
61 | In config\app.php, providers section:
62 |
63 | > Both the ServiceProvider and Facade Alias are auto registered by Laravel. There is no need to add them to the /config/app.php file.
64 |
65 | ~~Replace 'Illuminate\View\ViewServiceProvider::class' with 'Wpb\String_Blade_Compiler\ViewServiceProvider::class',~~
66 | > This version does not require you to remove the registration of the original view component. Upon ServiceProvider registration it will replace the view binds with its self.
67 |
68 | ### Laravel's Autoloader
69 |
70 | > There currently is a issue in Laravel's preload process of ServiceProviders. Service providers that are registered with the autoloaded are instantiated before service providers that are set in /config/app.php. This may cause problems in prior versions of StringBladeCompiler. The version has been rewitten to account for the autoloading process.
71 |
72 | > A pull request that would load vendor service providers registerd in the ```config/app.php``` file before autoloads, was sent to Laravel/Framework and was rejected.
73 |
74 | * If you have a need to have this, or any other, package load before the vendor autoloads, do this - https://gist.github.com/TerrePorter/4d2ed616c6ebeb371775347f7378c035
75 |
76 | Config
77 | =======================
78 |
79 | Default cache time for the compiled string template is 300 seconds (5 mins), this can be changed in the config file, env file, or when calling a view. The change is global to all string templates.
80 |
81 | `STRING_BLADE_CACHE_TIMEOUT=300`
82 |
83 | Autoloading of blade custom directives can be changed in the config and env files.
84 |
85 | `STRING_BLADE_AUTOLOAD=false`
86 |
87 | Note: If using homestead or some other vm, the host handles the filemtime of the cache file. This means the vm may have a different time than the file. If the cache is not expiring as expected, check the times between the systems.
88 |
89 | Usage
90 | =======================
91 |
92 | This package offers a StringView facade with the same syntax as View but accepts a Array or Array Object instance instead of path to view.
93 |
94 | ### New Config Option:
95 |
96 | Laravel 5.8 BladeCompiler adds a php comment to the compiled template file ```php $contents .= "getPath()} ENDPATH**/ ?>"; ```. Since StringBladeCompiler does not have a "path" aka "template file location" that would be helpful to the developer. I have included a new config value, ```templateRefKey```. This allows the developer to tag the StringBladeCompiler for where it is used. This is for if you end up digging in to the compiled view files, it would allow you to see a tag for StingBladeCompiler files.
97 |
98 | ### Config Options:
99 |
100 | ```php
101 | // existing file template load (the original View() method
102 | return view ('bladetemplatefile',['token' => 'I am the token value']);
103 | ```
104 | ```php
105 | // string blade template load
106 | return view (['template' => '{{$token}}'], ['token' => 'I am the token value']);
107 | ```
108 |
109 | ```php
110 | // you can mix the view types
111 | $preset = view (['template' => '{{$token}}'], ['token' => 'I am the token value']);
112 |
113 | return view ('bladetemplatefile', ['token' => $preset]);
114 | ```
115 |
116 | ```php
117 | // full list of options
118 | return view(
119 | array(
120 | // this actual blade template
121 | 'template' => '{{ $token1 }}',
122 |
123 | // this is the cache file key, converted to md5
124 | 'cache_key' => 'my_unique_cache_key',
125 |
126 | // number of seconds needed in order to recompile, 0 is always recompile
127 | 'secondsTemplateCacheExpires' => 1391973007,
128 |
129 | // sets the PATH comment value in the compiled file
130 | 'templateRefKey' => 'IndexController: Build function'
131 | ),
132 | array(
133 | 'token1'=> 'token 1 value'
134 | )
135 | );
136 | ```
137 |
138 | > Since StringBlade is a extend class from the original View. You should be able to do anything you would normally do with a View using StringBlade.
139 |
140 | ### Blade::extend, for example :
141 |
142 | As the compilers are set up as separate instances, if you need the extend on both the string and file template you will need to attach the extend (or directive) to both compilers.
143 |
144 | ```php
145 | // allows for @continue and @break in foreach in blade templates
146 | StringBlade::extend(function($value)
147 | {
148 | return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value);
149 | });
150 |
151 | Blade::extend(function($value)
152 | {
153 | return preg_replace('/(\s*)@(break|continue)(\s*)/', '$1$3', $value);
154 | });
155 | ```
156 |
157 | ### Other options,
158 |
159 | ```php
160 | // change the contente tags escaped or not
161 | StringBlade::setContentTagsEscaped(true);
162 |
163 | // for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires
164 | StringBlade::setForceTemplateRecompile(true);
165 | ```
166 |
167 | ```php
168 | // change the contente tags escaped or not
169 | Blade::setContentTagsEscaped(true);
170 |
171 | // for devel force templates to be rebuilt, ignores secondsTemplateCacheExpires
172 | Blade::setForceTemplateRecompile(true);
173 | ```
174 |
175 | ```php
176 | // @deprecated This feature was removed from Laravel (https://github.com/laravel/framework/issues/17736)
177 |
178 | // change the tags
179 | StringBlade::setRawTags('[!!', '!!]',escapeFlag); // default {!! !!}
180 | StringBlade::setContentTags('[[', ']]',escapeFlag); // default {{ }}
181 | StringBlade::setEscapedTags('[[[', ']]]',escapeFlag); // default {{{ }}}
182 |
183 | __ Functions are still there, use at your own risk. __
184 | ```
185 |
186 | ~~Deleting generated compiled cach view files (v3+).Set the delete flag for the compiler being used, stringblade or blade~~
187 |
188 | > I can't seem to find when the setting was actually used. If you want this, submit a bug and I will see about adding the ability.
189 |
190 | ```php
191 | // set flag to delete compiled view cache files after rendering for stringblade compiler
192 | View::getEngineFromStringKey('stringblade')->setDeleteViewCacheAfterRender(true);
193 |
194 | // set flag to delete compiled view cache files after rendering for blade compiler
195 | View::getEngineFromStringKey('blade')->setDeleteViewCacheAfterRender(true);
196 | ```
197 |
198 | License
199 | =======================
200 |
201 | string-blade-compiler is open-sourced software licensed under the MIT license
202 |
--------------------------------------------------------------------------------
/tests/View/ViewTest.php:
--------------------------------------------------------------------------------
1 | getView();
29 | $view->with('foo', 'bar');
30 | $view->with(['baz' => 'boom']);
31 | $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData());
32 |
33 | $view = $this->getView();
34 | $view->withFoo('bar')->withBaz('boom');
35 | $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData());
36 | }
37 |
38 | public function testRenderProperlyRendersView()
39 | {
40 | $view = $this->getView(['foo' => 'bar']);
41 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
42 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
43 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
44 | $view->getEngine()->shouldReceive('get')->once()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents');
45 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
46 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
47 |
48 | $callback = function (View $rendered, $contents) use ($view) {
49 | $this->assertEquals($view, $rendered);
50 | $this->assertEquals('contents', $contents);
51 | };
52 |
53 | $this->assertEquals('contents', $view->render($callback));
54 | }
55 |
56 | public function testRenderHandlingCallbackReturnValues()
57 | {
58 | $view = $this->getView();
59 | $view->getFactory()->shouldReceive('incrementRender');
60 | $view->getFactory()->shouldReceive('callComposer');
61 | $view->getFactory()->shouldReceive('getShared')->andReturn(['shared' => 'foo']);
62 | $view->getEngine()->shouldReceive('get')->andReturn('contents');
63 | $view->getFactory()->shouldReceive('decrementRender');
64 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering');
65 |
66 | $this->assertEquals('new contents', $view->render(function () {
67 | return 'new contents';
68 | }));
69 |
70 | $this->assertEmpty($view->render(function () {
71 | return '';
72 | }));
73 |
74 | $this->assertEquals('contents', $view->render(function () {
75 | //
76 | }));
77 | }
78 |
79 | public function testRenderSectionsReturnsEnvironmentSections()
80 | {
81 | $view = m::mock(View::class.'[render]', [
82 | m::mock(Factory::class),
83 | m::mock(Engine::class),
84 | 'view',
85 | 'path',
86 | [],
87 | ]);
88 |
89 | $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']);
90 |
91 | $this->assertEquals($sections, $view->renderSections());
92 | }
93 |
94 | public function testSectionsAreNotFlushedWhenNotDoneRendering()
95 | {
96 | $view = $this->getView(['foo' => 'bar']);
97 | $view->getFactory()->shouldReceive('incrementRender')->twice();
98 | $view->getFactory()->shouldReceive('callComposer')->twice()->with($view);
99 | $view->getFactory()->shouldReceive('getShared')->twice()->andReturn(['shared' => 'foo']);
100 | $view->getEngine()->shouldReceive('get')->twice()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents');
101 | $view->getFactory()->shouldReceive('decrementRender')->twice();
102 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->twice();
103 |
104 | $this->assertEquals('contents', $view->render());
105 | $this->assertEquals('contents', (string) $view);
106 | }
107 |
108 | public function testViewNestBindsASubView()
109 | {
110 | $view = $this->getView();
111 | $view->getFactory()->shouldReceive('make')->once()->with('foo', ['data']);
112 | $result = $view->nest('key', 'foo', ['data']);
113 |
114 | $this->assertInstanceOf(View::class, $result);
115 | }
116 |
117 | public function testViewAcceptsArrayableImplementations()
118 | {
119 | $arrayable = m::mock(Arrayable::class);
120 | $arrayable->shouldReceive('toArray')->once()->andReturn(['foo' => 'bar', 'baz' => ['qux', 'corge']]);
121 |
122 | $view = $this->getView($arrayable);
123 |
124 | $this->assertEquals('bar', $view->foo);
125 | $this->assertEquals(['qux', 'corge'], $view->baz);
126 | }
127 |
128 | public function testViewGettersSetters()
129 | {
130 | $view = $this->getView(['foo' => 'bar']);
131 | $this->assertEquals($view->name(), 'view');
132 | $this->assertEquals($view->getPath(), 'path');
133 | $data = $view->getData();
134 | $this->assertEquals($data['foo'], 'bar');
135 | $view->setPath('newPath');
136 | $this->assertEquals($view->getPath(), 'newPath');
137 | }
138 |
139 | public function testViewArrayAccess()
140 | {
141 | $view = $this->getView(['foo' => 'bar']);
142 | $this->assertInstanceOf(ArrayAccess::class, $view);
143 | $this->assertTrue($view->offsetExists('foo'));
144 | $this->assertEquals($view->offsetGet('foo'), 'bar');
145 | $view->offsetSet('foo', 'baz');
146 | $this->assertEquals($view->offsetGet('foo'), 'baz');
147 | $view->offsetUnset('foo');
148 | $this->assertFalse($view->offsetExists('foo'));
149 | }
150 |
151 | public function testViewConstructedWithObjectData()
152 | {
153 | $view = $this->getView(new DataObjectStub);
154 | $this->assertInstanceOf(ArrayAccess::class, $view);
155 | $this->assertTrue($view->offsetExists('foo'));
156 | $this->assertEquals($view->offsetGet('foo'), 'bar');
157 | $view->offsetSet('foo', 'baz');
158 | $this->assertEquals($view->offsetGet('foo'), 'baz');
159 | $view->offsetUnset('foo');
160 | $this->assertFalse($view->offsetExists('foo'));
161 | }
162 |
163 | public function testViewMagicMethods()
164 | {
165 | $view = $this->getView(['foo' => 'bar']);
166 | $this->assertTrue(isset($view->foo));
167 | $this->assertEquals($view->foo, 'bar');
168 | $view->foo = 'baz';
169 | $this->assertEquals($view->foo, 'baz');
170 | $this->assertEquals($view['foo'], $view->foo);
171 | unset($view->foo);
172 | $this->assertFalse(isset($view->foo));
173 | $this->assertFalse($view->offsetExists('foo'));
174 | }
175 |
176 | public function testViewBadMethod()
177 | {
178 | $this->expectException(BadMethodCallException::class);
179 | $this->expectExceptionMessage('Method Wpb\String_Blade_Compiler\View::badMethodCall does not exist.');
180 |
181 | $view = $this->getView();
182 | $view->badMethodCall();
183 | }
184 |
185 | public function testViewGatherDataWithRenderable()
186 | {
187 | $view = $this->getView();
188 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
189 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
190 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
191 | $view->getEngine()->shouldReceive('get')->once()->andReturn('contents');
192 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
193 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
194 |
195 | $view->renderable = m::mock(Renderable::class);
196 | $view->renderable->shouldReceive('render')->once()->andReturn('text');
197 | $this->assertEquals('contents', $view->render());
198 | }
199 |
200 | public function testViewRenderSections()
201 | {
202 | $view = $this->getView();
203 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
204 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
205 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
206 | $view->getEngine()->shouldReceive('get')->once()->andReturn('contents');
207 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
208 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
209 |
210 | $view->getFactory()->shouldReceive('getSections')->once()->andReturn(['foo', 'bar']);
211 | $sections = $view->renderSections();
212 | $this->assertEquals($sections[0], 'foo');
213 | $this->assertEquals($sections[1], 'bar');
214 | }
215 |
216 | public function testWithErrors()
217 | {
218 | $view = $this->getView();
219 | $errors = ['foo' => 'bar', 'qu' => 'ux'];
220 | $this->assertSame($view, $view->withErrors($errors));
221 | $this->assertInstanceOf(MessageBag::class, $view->errors);
222 | $foo = $view->errors->get('foo');
223 | $this->assertEquals($foo[0], 'bar');
224 | $qu = $view->errors->get('qu');
225 | $this->assertEquals($qu[0], 'ux');
226 | $data = ['foo' => 'baz'];
227 | $this->assertSame($view, $view->withErrors(new MessageBag($data)));
228 | $foo = $view->errors->get('foo');
229 | $this->assertEquals($foo[0], 'baz');
230 | }
231 |
232 | protected function getView($data = [])
233 | {
234 | return new View(
235 | m::mock(Factory::class),
236 | m::mock(Engine::class),
237 | 'view',
238 | 'path',
239 | $data
240 | );
241 | }
242 | }
243 |
244 |
--------------------------------------------------------------------------------
/tests/View/StringViewTest.php:
--------------------------------------------------------------------------------
1 | getView();
29 | $view->with('foo', 'bar');
30 | $view->with(['baz' => 'boom']);
31 | $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData());
32 |
33 | $view = $this->getView();
34 | $view->withFoo('bar')->withBaz('boom');
35 | $this->assertEquals(['foo' => 'bar', 'baz' => 'boom'], $view->getData());
36 | }
37 |
38 | public function testRenderProperlyRendersView()
39 | {
40 | $view = $this->getView(['foo' => 'bar']);
41 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
42 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
43 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
44 | $view->getEngine()->shouldReceive('get')->once()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents');
45 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
46 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
47 |
48 | $callback = function (View $rendered, $contents) use ($view) {
49 | $this->assertEquals($view, $rendered);
50 | $this->assertEquals('contents', $contents);
51 | };
52 |
53 | $this->assertEquals('contents', $view->render($callback));
54 | }
55 |
56 | public function testRenderHandlingCallbackReturnValues()
57 | {
58 | $view = $this->getView();
59 | $view->getFactory()->shouldReceive('incrementRender');
60 | $view->getFactory()->shouldReceive('callComposer');
61 | $view->getFactory()->shouldReceive('getShared')->andReturn(['shared' => 'foo']);
62 | $view->getEngine()->shouldReceive('get')->andReturn('contents');
63 | $view->getFactory()->shouldReceive('decrementRender');
64 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering');
65 |
66 | $this->assertEquals('new contents', $view->render(function () {
67 | return 'new contents';
68 | }));
69 |
70 | $this->assertEmpty($view->render(function () {
71 | return '';
72 | }));
73 |
74 | $this->assertEquals('contents', $view->render(function () {
75 | //
76 | }));
77 | }
78 |
79 | public function testRenderSectionsReturnsEnvironmentSections()
80 | {
81 | $view = m::mock(View::class.'[render]', [
82 | m::mock(Factory::class),
83 | m::mock(Engine::class),
84 | [
85 | // this actual blade template
86 | 'template' => '{{ $token1 }}',
87 | // this is the cache file key, converted to md5
88 | 'cache_key' => 'my_unique_cache_key',
89 | // number of seconds needed in order to recompile, 0 is always recompile
90 | 'secondsTemplateCacheExpires' => 1391973007
91 | ],
92 | 'path',
93 | [],
94 | ]);
95 |
96 | $view->shouldReceive('render')->with(m::type(Closure::class))->once()->andReturn($sections = ['foo' => 'bar']);
97 |
98 | $this->assertEquals($sections, $view->renderSections());
99 | }
100 |
101 | public function testSectionsAreNotFlushedWhenNotDoneRendering()
102 | {
103 | $view = $this->getView(['foo' => 'bar']);
104 | $view->getFactory()->shouldReceive('incrementRender')->twice();
105 | $view->getFactory()->shouldReceive('callComposer')->twice()->with($view);
106 | $view->getFactory()->shouldReceive('getShared')->twice()->andReturn(['shared' => 'foo']);
107 | $view->getEngine()->shouldReceive('get')->twice()->with('path', ['foo' => 'bar', 'shared' => 'foo'])->andReturn('contents');
108 | $view->getFactory()->shouldReceive('decrementRender')->twice();
109 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->twice();
110 |
111 | $this->assertEquals('contents', $view->render());
112 | $this->assertEquals('contents', (string) $view);
113 | }
114 |
115 | public function testViewNestBindsASubView()
116 | {
117 | $view = $this->getView();
118 | $view->getFactory()->shouldReceive('make')->once()->with('foo', ['data']);
119 | $result = $view->nest('key', 'foo', ['data']);
120 |
121 | $this->assertInstanceOf(View::class, $result);
122 | }
123 |
124 | public function testViewAcceptsArrayableImplementations()
125 | {
126 | $arrayable = m::mock(Arrayable::class);
127 | $arrayable->shouldReceive('toArray')->once()->andReturn(['foo' => 'bar', 'baz' => ['qux', 'corge']]);
128 |
129 | $view = $this->getView($arrayable);
130 |
131 | $this->assertEquals('bar', $view->foo);
132 | $this->assertEquals(['qux', 'corge'], $view->baz);
133 | }
134 |
135 | public function testViewGettersSetters()
136 | {
137 | $view = $this->getView(['foo' => 'bar']);
138 | $this->assertEquals($view->name(), md5($view->getViewTemplate()));
139 | $this->assertEquals($view->getPath(), 'path');
140 | $data = $view->getData();
141 | $this->assertEquals($data['foo'], 'bar');
142 | $view->setPath('newPath');
143 | $this->assertEquals($view->getPath(), 'newPath');
144 | }
145 |
146 | public function testViewArrayAccess()
147 | {
148 | $view = $this->getView(['foo' => 'bar']);
149 | $this->assertInstanceOf(ArrayAccess::class, $view);
150 | $this->assertTrue($view->offsetExists('foo'));
151 | $this->assertEquals($view->offsetGet('foo'), 'bar');
152 | $view->offsetSet('foo', 'baz');
153 | $this->assertEquals($view->offsetGet('foo'), 'baz');
154 | $view->offsetUnset('foo');
155 | $this->assertFalse($view->offsetExists('foo'));
156 | }
157 |
158 | public function testViewConstructedWithObjectData()
159 | {
160 | $view = $this->getView(new DataObjectStub);
161 | $this->assertInstanceOf(ArrayAccess::class, $view);
162 | $this->assertTrue($view->offsetExists('foo'));
163 | $this->assertEquals($view->offsetGet('foo'), 'bar');
164 | $view->offsetSet('foo', 'baz');
165 | $this->assertEquals($view->offsetGet('foo'), 'baz');
166 | $view->offsetUnset('foo');
167 | $this->assertFalse($view->offsetExists('foo'));
168 | }
169 |
170 | public function testViewMagicMethods()
171 | {
172 | $view = $this->getView(['foo' => 'bar']);
173 | $this->assertTrue(isset($view->foo));
174 | $this->assertEquals($view->foo, 'bar');
175 | $view->foo = 'baz';
176 | $this->assertEquals($view->foo, 'baz');
177 | $this->assertEquals($view['foo'], $view->foo);
178 | unset($view->foo);
179 | $this->assertFalse(isset($view->foo));
180 | $this->assertFalse($view->offsetExists('foo'));
181 | }
182 |
183 | public function testViewBadMethod()
184 | {
185 | $this->expectException(BadMethodCallException::class);
186 | $this->expectExceptionMessage('Method Wpb\String_Blade_Compiler\StringView::badMethodCall does not exist.');
187 |
188 | $view = $this->getView();
189 | $view->badMethodCall();
190 | }
191 |
192 | public function testViewGatherDataWithRenderable()
193 | {
194 | $view = $this->getView();
195 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
196 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
197 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
198 | $view->getEngine()->shouldReceive('get')->once()->andReturn('contents');
199 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
200 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
201 |
202 | $view->renderable = m::mock(Renderable::class);
203 | $view->renderable->shouldReceive('render')->once()->andReturn('text');
204 | $this->assertEquals('contents', $view->render());
205 | }
206 |
207 | public function testViewRenderSections()
208 | {
209 | $view = $this->getView();
210 | $view->getFactory()->shouldReceive('incrementRender')->once()->ordered();
211 | $view->getFactory()->shouldReceive('callComposer')->once()->ordered()->with($view);
212 | $view->getFactory()->shouldReceive('getShared')->once()->andReturn(['shared' => 'foo']);
213 | $view->getEngine()->shouldReceive('get')->once()->andReturn('contents');
214 | $view->getFactory()->shouldReceive('decrementRender')->once()->ordered();
215 | $view->getFactory()->shouldReceive('flushStateIfDoneRendering')->once();
216 |
217 | $view->getFactory()->shouldReceive('getSections')->once()->andReturn(['foo', 'bar']);
218 | $sections = $view->renderSections();
219 | $this->assertEquals($sections[0], 'foo');
220 | $this->assertEquals($sections[1], 'bar');
221 | }
222 |
223 | public function testWithErrors()
224 | {
225 | $view = $this->getView();
226 | $errors = ['foo' => 'bar', 'qu' => 'ux'];
227 | $this->assertSame($view, $view->withErrors($errors));
228 | $this->assertInstanceOf(MessageBag::class, $view->errors);
229 | $foo = $view->errors->get('foo');
230 | $this->assertEquals($foo[0], 'bar');
231 | $qu = $view->errors->get('qu');
232 | $this->assertEquals($qu[0], 'ux');
233 | $data = ['foo' => 'baz'];
234 | $this->assertSame($view, $view->withErrors(new MessageBag($data)));
235 | $foo = $view->errors->get('foo');
236 | $this->assertEquals($foo[0], 'baz');
237 | }
238 |
239 | protected function getView($data = [])
240 | {
241 | return new View(
242 | m::mock(Factory::class),
243 | m::mock(Engine::class),
244 | [
245 | // this actual blade template
246 | 'template' => '{{ $token1 }}',
247 | // this is the cache file key, converted to md5
248 | 'cache_key' => 'my_unique_cache_key',
249 | // number of seconds needed in order to recompile, 0 is always recompile
250 | 'secondsTemplateCacheExpires' => 1391973007
251 | ],
252 | 'path',
253 | $data
254 | );
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/tests/View/ViewFactoryTest.php:
--------------------------------------------------------------------------------
1 | getFactory();
38 | $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.php');
39 | $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class));
40 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('php');
41 | $factory->setDispatcher(new Dispatcher);
42 | $factory->creator('view', function ($view) {
43 | $_SERVER['__test.view'] = $view;
44 | });
45 | $factory->addExtension('php', 'php');
46 | $view = $factory->make('view', ['foo' => 'bar'], ['baz' => 'boom']);
47 |
48 | $this->assertSame($engine, $view->getEngine());
49 | $this->assertSame($_SERVER['__test.view'], $view);
50 |
51 | unset($_SERVER['__test.view']);
52 | }
53 |
54 | public function testExistsPassesAndFailsViews()
55 | {
56 | $factory = $this->getFactory();
57 | $factory->getFinder()->shouldReceive('find')->once()->with('foo')->andThrow(InvalidArgumentException::class);
58 | $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andReturn('path.php');
59 |
60 | $this->assertFalse($factory->exists('foo'));
61 | $this->assertTrue($factory->exists('bar'));
62 | }
63 |
64 | public function testFirstCreatesNewViewInstanceWithProperPath()
65 | {
66 | unset($_SERVER['__test.view']);
67 |
68 | $factory = $this->getFactory();
69 | $factory->getFinder()->shouldReceive('find')->twice()->with('view')->andReturn('path.php');
70 | $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class);
71 | $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('php')->andReturn($engine = m::mock(Engine::class));
72 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('php');
73 | $factory->setDispatcher(new Dispatcher);
74 | $factory->creator('view', function ($view) {
75 | $_SERVER['__test.view'] = $view;
76 | });
77 | $factory->addExtension('php', 'php');
78 | $view = $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']);
79 |
80 | $this->assertSame($engine, $view->getEngine());
81 | $this->assertSame($_SERVER['__test.view'], $view);
82 |
83 | unset($_SERVER['__test.view']);
84 | }
85 |
86 | public function testFirstThrowsInvalidArgumentExceptionIfNoneFound()
87 | {
88 | $this->expectException(InvalidArgumentException::class);
89 |
90 | $factory = $this->getFactory();
91 | $factory->getFinder()->shouldReceive('find')->once()->with('view')->andThrow(InvalidArgumentException::class);
92 | $factory->getFinder()->shouldReceive('find')->once()->with('bar')->andThrow(InvalidArgumentException::class);
93 | $factory->getEngineResolver()->shouldReceive('resolve')->with('php')->andReturn($engine = m::mock(Engine::class));
94 | $factory->getFinder()->shouldReceive('addExtension')->with('php');
95 | $factory->addExtension('php', 'php');
96 | $factory->first(['bar', 'view'], ['foo' => 'bar'], ['baz' => 'boom']);
97 | }
98 |
99 | public function testRenderEachCreatesViewForEachItemInArray()
100 | {
101 | $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs());
102 | $factory->shouldReceive('make')->once()->with('foo', ['key' => 'bar', 'value' => 'baz'])->andReturn($mockView1 = m::mock(stdClass::class));
103 | $factory->shouldReceive('make')->once()->with('foo', ['key' => 'breeze', 'value' => 'boom'])->andReturn($mockView2 = m::mock(stdClass::class));
104 | $mockView1->shouldReceive('render')->once()->andReturn('dayle');
105 | $mockView2->shouldReceive('render')->once()->andReturn('rees');
106 |
107 | $result = $factory->renderEach('foo', ['bar' => 'baz', 'breeze' => 'boom'], 'value');
108 |
109 | $this->assertEquals('daylerees', $result);
110 | }
111 |
112 | public function testEmptyViewsCanBeReturnedFromRenderEach()
113 | {
114 | $factory = m::mock(Factory::class.'[make]', $this->getFactoryArgs());
115 | $factory->shouldReceive('make')->once()->with('foo')->andReturn($mockView = m::mock(stdClass::class));
116 | $mockView->shouldReceive('render')->once()->andReturn('empty');
117 |
118 | $this->assertEquals('empty', $factory->renderEach('view', [], 'iterator', 'foo'));
119 | }
120 |
121 | public function testRawStringsMayBeReturnedFromRenderEach()
122 | {
123 | $this->assertEquals('foo', $this->getFactory()->renderEach('foo', [], 'item', 'raw|foo'));
124 | }
125 |
126 | public function testEnvironmentAddsExtensionWithCustomResolver()
127 | {
128 | $factory = $this->getFactory();
129 |
130 | $resolver = function () {
131 | //
132 | };
133 |
134 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
135 | $factory->getEngineResolver()->shouldReceive('register')->once()->with('bar', $resolver);
136 | $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.foo');
137 | $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock(Engine::class));
138 | $factory->getDispatcher()->shouldReceive('dispatch');
139 |
140 | $factory->addExtension('foo', 'bar', $resolver);
141 |
142 | $view = $factory->make('view', ['data']);
143 | $this->assertSame($engine, $view->getEngine());
144 | }
145 |
146 | public function testAddingExtensionPrependsNotAppends()
147 | {
148 | $factory = $this->getFactory();
149 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
150 |
151 | $factory->addExtension('foo', 'bar');
152 |
153 | $extensions = $factory->getExtensions();
154 | $this->assertEquals('bar', reset($extensions));
155 | $this->assertEquals('foo', key($extensions));
156 | }
157 |
158 | public function testPrependedExtensionOverridesExistingExtensions()
159 | {
160 | $factory = $this->getFactory();
161 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('foo');
162 | $factory->getFinder()->shouldReceive('addExtension')->once()->with('baz');
163 |
164 | $factory->addExtension('foo', 'bar');
165 | $factory->addExtension('baz', 'bar');
166 |
167 | $extensions = $factory->getExtensions();
168 | $this->assertEquals('bar', reset($extensions));
169 | $this->assertEquals('baz', key($extensions));
170 | }
171 |
172 | public function testComposersAreProperlyRegistered()
173 | {
174 | $factory = $this->getFactory();
175 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
176 | $callback = $factory->composer('foo', function () {
177 | return 'bar';
178 | });
179 | $callback = $callback[0];
180 |
181 | $this->assertEquals('bar', $callback());
182 | }
183 |
184 | public function testComposersCanBeMassRegistered()
185 | {
186 | $factory = $this->getFactory();
187 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: bar', m::type(Closure::class));
188 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: qux', m::type(Closure::class));
189 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
190 | $composers = $factory->composers([
191 | 'foo' => 'bar',
192 | 'baz@baz' => ['qux', 'foo'],
193 | ]);
194 |
195 | $this->assertCount(3, $composers);
196 | $reflections = [
197 | new ReflectionFunction($composers[0]),
198 | new ReflectionFunction($composers[1]),
199 | ];
200 | $this->assertEquals(['class' => 'foo', 'method' => 'compose'], $reflections[0]->getStaticVariables());
201 | $this->assertEquals(['class' => 'baz', 'method' => 'baz'], $reflections[1]->getStaticVariables());
202 | }
203 |
204 | public function testClassCallbacks()
205 | {
206 | $factory = $this->getFactory();
207 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
208 | $factory->setContainer($container = m::mock(Container::class));
209 | $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class));
210 | $composer->shouldReceive('compose')->once()->with('view')->andReturn('composed');
211 | $callback = $factory->composer('foo', 'FooComposer');
212 | $callback = $callback[0];
213 |
214 | $this->assertEquals('composed', $callback('view'));
215 | }
216 |
217 | public function testClassCallbacksWithMethods()
218 | {
219 | $factory = $this->getFactory();
220 | $factory->getDispatcher()->shouldReceive('listen')->once()->with('composing: foo', m::type(Closure::class));
221 | $factory->setContainer($container = m::mock(Container::class));
222 | $container->shouldReceive('make')->once()->with('FooComposer')->andReturn($composer = m::mock(stdClass::class));
223 | $composer->shouldReceive('doComposer')->once()->with('view')->andReturn('composed');
224 | $callback = $factory->composer('foo', 'FooComposer@doComposer');
225 | $callback = $callback[0];
226 |
227 | $this->assertEquals('composed', $callback('view'));
228 | }
229 |
230 | public function testCallComposerCallsProperEvent()
231 | {
232 | $factory = $this->getFactory();
233 | $view = m::mock(View::class);
234 | $view->shouldReceive('name')->once()->andReturn('name');
235 | $factory->getDispatcher()->shouldReceive('dispatch')->once()->with('composing: name', [$view]);
236 |
237 | $factory->callComposer($view);
238 | }
239 |
240 | public function testComposersAreRegisteredWithSlashAndDot()
241 | {
242 | $factory = $this->getFactory();
243 | $factory->getDispatcher()->shouldReceive('listen')->with('composing: foo.bar', m::any())->twice();
244 | $factory->composer('foo.bar', '');
245 | $factory->composer('foo/bar', '');
246 | }
247 |
248 | public function testRenderCountHandling()
249 | {
250 | $factory = $this->getFactory();
251 | $factory->incrementRender();
252 | $this->assertFalse($factory->doneRendering());
253 | $factory->decrementRender();
254 | $this->assertTrue($factory->doneRendering());
255 | }
256 |
257 | public function testYieldDefault()
258 | {
259 | $factory = $this->getFactory();
260 | $this->assertEquals('hi', $factory->yieldContent('foo', 'hi'));
261 | }
262 |
263 | public function testYieldDefaultIsEscaped()
264 | {
265 | $factory = $this->getFactory();
266 | $this->assertEquals('<p>hi</p>', $factory->yieldContent('foo', '
hi
'));
267 | }
268 |
269 | public function testYieldDefaultViewIsNotEscapedTwice()
270 | {
271 | $factory = $this->getFactory();
272 | $view = m::mock(View::class);
273 | $view->shouldReceive('__toString')->once()->andReturn('hi
<p>already escaped</p>');
274 | $this->assertEquals('hi
<p>already escaped</p>', $factory->yieldContent('foo', $view));
275 | }
276 |
277 | public function testBasicSectionHandling()
278 | {
279 | $factory = $this->getFactory();
280 | $factory->startSection('foo');
281 | echo 'hi';
282 | $factory->stopSection();
283 | $this->assertEquals('hi', $factory->yieldContent('foo'));
284 | }
285 |
286 | public function testBasicSectionDefault()
287 | {
288 | $factory = $this->getFactory();
289 | $factory->startSection('foo', 'hi');
290 | $this->assertEquals('hi', $factory->yieldContent('foo'));
291 | }
292 |
293 | public function testBasicSectionDefaultIsEscaped()
294 | {
295 | $factory = $this->getFactory();
296 | $factory->startSection('foo', 'hi
');
297 | $this->assertEquals('<p>hi</p>', $factory->yieldContent('foo'));
298 | }
299 |
300 | public function testBasicSectionDefaultViewIsNotEscapedTwice()
301 | {
302 | $factory = $this->getFactory();
303 | $view = m::mock(View::class);
304 | $view->shouldReceive('__toString')->once()->andReturn('hi
<p>already escaped</p>');
305 | $factory->startSection('foo', $view);
306 | $this->assertEquals('hi
<p>already escaped</p>', $factory->yieldContent('foo'));
307 | }
308 |
309 | public function testSectionExtending()
310 | {
311 | $placeholder = Factory::parentPlaceholder('foo');
312 | $factory = $this->getFactory();
313 | $factory->startSection('foo');
314 | echo 'hi '.$placeholder;
315 | $factory->stopSection();
316 | $factory->startSection('foo');
317 | echo 'there';
318 | $factory->stopSection();
319 | $this->assertEquals('hi there', $factory->yieldContent('foo'));
320 | }
321 |
322 | public function testSectionMultipleExtending()
323 | {
324 | $placeholder = Factory::parentPlaceholder('foo');
325 | $factory = $this->getFactory();
326 | $factory->startSection('foo');
327 | echo 'hello '.$placeholder.' nice to see you '.$placeholder;
328 | $factory->stopSection();
329 | $factory->startSection('foo');
330 | echo 'my '.$placeholder;
331 | $factory->stopSection();
332 | $factory->startSection('foo');
333 | echo 'friend';
334 | $factory->stopSection();
335 | $this->assertEquals('hello my friend nice to see you my friend', $factory->yieldContent('foo'));
336 | }
337 |
338 | public function testComponentHandling()
339 | {
340 | $factory = $this->getFactory();
341 | $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php');
342 | $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine);
343 | $factory->getDispatcher()->shouldReceive('dispatch');
344 | $factory->startComponent('component', ['name' => 'Taylor']);
345 | $factory->slot('title');
346 | $factory->slot('website', 'laravel.com');
347 | echo 'title
';
348 | $factory->endSlot();
349 | echo 'component';
350 | $contents = $factory->renderComponent();
351 | $this->assertEquals('title
component Taylor laravel.com', $contents);
352 | }
353 |
354 | public function testTranslation()
355 | {
356 | $container = new Container;
357 | $container->instance('translator', $translator = m::mock(stdClass::class));
358 | $translator->shouldReceive('getFromJson')->with('Foo', ['name' => 'taylor'])->andReturn('Bar');
359 | $factory = $this->getFactory();
360 | $factory->setContainer($container);
361 | $factory->startTranslation(['name' => 'taylor']);
362 | echo 'Foo';
363 | $string = $factory->renderTranslation();
364 |
365 | $this->assertEquals('Bar', $string);
366 | }
367 |
368 | public function testSingleStackPush()
369 | {
370 | $factory = $this->getFactory();
371 | $factory->startPush('foo');
372 | echo 'hi';
373 | $factory->stopPush();
374 | $this->assertEquals('hi', $factory->yieldPushContent('foo'));
375 | }
376 |
377 | public function testMultipleStackPush()
378 | {
379 | $factory = $this->getFactory();
380 | $factory->startPush('foo');
381 | echo 'hi';
382 | $factory->stopPush();
383 | $factory->startPush('foo');
384 | echo ', Hello!';
385 | $factory->stopPush();
386 | $this->assertEquals('hi, Hello!', $factory->yieldPushContent('foo'));
387 | }
388 |
389 | public function testSessionAppending()
390 | {
391 | $factory = $this->getFactory();
392 | $factory->startSection('foo');
393 | echo 'hi';
394 | $factory->appendSection();
395 | $factory->startSection('foo');
396 | echo 'there';
397 | $factory->appendSection();
398 | $this->assertEquals('hithere', $factory->yieldContent('foo'));
399 | }
400 |
401 | public function testYieldSectionStopsAndYields()
402 | {
403 | $factory = $this->getFactory();
404 | $factory->startSection('foo');
405 | echo 'hi';
406 | $this->assertEquals('hi', $factory->yieldSection());
407 | }
408 |
409 | public function testInjectStartsSectionWithContent()
410 | {
411 | $factory = $this->getFactory();
412 | $factory->inject('foo', 'hi');
413 | $this->assertEquals('hi', $factory->yieldContent('foo'));
414 | }
415 |
416 | public function testEmptyStringIsReturnedForNonSections()
417 | {
418 | $factory = $this->getFactory();
419 | $this->assertEmpty($factory->yieldContent('foo'));
420 | }
421 |
422 | public function testSectionFlushing()
423 | {
424 | $factory = $this->getFactory();
425 | $factory->startSection('foo');
426 | echo 'hi';
427 | $factory->stopSection();
428 |
429 | $this->assertCount(1, $factory->getSections());
430 |
431 | $factory->flushSections();
432 |
433 | $this->assertCount(0, $factory->getSections());
434 | }
435 |
436 | public function testHasSection()
437 | {
438 | $factory = $this->getFactory();
439 | $factory->startSection('foo');
440 | echo 'hi';
441 | $factory->stopSection();
442 |
443 | $this->assertTrue($factory->hasSection('foo'));
444 | $this->assertFalse($factory->hasSection('bar'));
445 | }
446 |
447 | public function testGetSection()
448 | {
449 | $factory = $this->getFactory();
450 | $factory->startSection('foo');
451 | echo 'hi';
452 | $factory->stopSection();
453 |
454 | $this->assertEquals('hi', $factory->getSection('foo'));
455 | $this->assertNull($factory->getSection('bar'));
456 | $this->assertEquals('default', $factory->getSection('bar', 'default'));
457 | }
458 |
459 | public function testMakeWithSlashAndDot()
460 | {
461 | $factory = $this->getFactory();
462 | $factory->getFinder()->shouldReceive('find')->twice()->with('foo.bar')->andReturn('path.php');
463 | $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class));
464 | $factory->getDispatcher()->shouldReceive('dispatch');
465 | $factory->make('foo/bar');
466 | $factory->make('foo.bar');
467 | }
468 |
469 | public function testNamespacedViewNamesAreNormalizedProperly()
470 | {
471 | $factory = $this->getFactory();
472 | $factory->getFinder()->shouldReceive('find')->twice()->with('vendor/package::foo.bar')->andReturn('path.php');
473 | $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class));
474 | $factory->getDispatcher()->shouldReceive('dispatch');
475 | $factory->make('vendor/package::foo/bar');
476 | $factory->make('vendor/package::foo.bar');
477 | }
478 |
479 | public function testExceptionIsThrownForUnknownExtension()
480 | {
481 | $this->expectException(InvalidArgumentException::class);
482 |
483 | $factory = $this->getFactory();
484 | $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('view.foo');
485 | $factory->make('view');
486 | }
487 |
488 | public function testExceptionsInSectionsAreThrown()
489 | {
490 | $this->expectException(ErrorException::class);
491 | $this->expectExceptionMessage('section exception message');
492 |
493 | $engine = new CompilerEngine(m::mock(CompilerInterface::class));
494 | $engine->getCompiler()->shouldReceive('getCompiledPath')->andReturnUsing(function ($path) {
495 | return $path;
496 | });
497 | $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false);
498 | $factory = $this->getFactory();
499 | $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine);
500 | $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php');
501 | $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php');
502 | $factory->getDispatcher()->shouldReceive('dispatch')->times(4);
503 |
504 | $factory->make('view')->render();
505 | }
506 |
507 | public function testExtraStopSectionCallThrowsException()
508 | {
509 | $this->expectException(InvalidArgumentException::class);
510 | $this->expectExceptionMessage('Cannot end a section without first starting one.');
511 |
512 | $factory = $this->getFactory();
513 | $factory->startSection('foo');
514 | $factory->stopSection();
515 |
516 | $factory->stopSection();
517 | }
518 |
519 | public function testExtraAppendSectionCallThrowsException()
520 | {
521 | $this->expectException(InvalidArgumentException::class);
522 | $this->expectExceptionMessage('Cannot end a section without first starting one.');
523 |
524 | $factory = $this->getFactory();
525 | $factory->startSection('foo');
526 | $factory->stopSection();
527 |
528 | $factory->appendSection();
529 | }
530 |
531 | public function testAddingLoops()
532 | {
533 | $factory = $this->getFactory();
534 |
535 | $factory->addLoop([1, 2, 3]);
536 |
537 | $expectedLoop = [
538 | 'iteration' => 0,
539 | 'index' => 0,
540 | 'remaining' => 3,
541 | 'count' => 3,
542 | 'first' => true,
543 | 'last' => false,
544 | 'odd' => false,
545 | 'even' => true,
546 | 'depth' => 1,
547 | 'parent' => null,
548 | ];
549 |
550 | $this->assertEquals([$expectedLoop], $factory->getLoopStack());
551 |
552 | $factory->addLoop([1, 2, 3, 4]);
553 |
554 | $secondExpectedLoop = [
555 | 'iteration' => 0,
556 | 'index' => 0,
557 | 'remaining' => 4,
558 | 'count' => 4,
559 | 'first' => true,
560 | 'last' => false,
561 | 'odd' => false,
562 | 'even' => true,
563 | 'depth' => 2,
564 | 'parent' => (object) $expectedLoop,
565 | ];
566 | $this->assertEquals([$expectedLoop, $secondExpectedLoop], $factory->getLoopStack());
567 |
568 | $factory->popLoop();
569 |
570 | $this->assertEquals([$expectedLoop], $factory->getLoopStack());
571 | }
572 |
573 | public function testAddingLoopDoesNotCloseGenerator()
574 | {
575 | $factory = $this->getFactory();
576 |
577 | $data = (new class {
578 | public function generate()
579 | {
580 | for ($count = 0; $count < 3; $count++) {
581 | yield ['a', 'b'];
582 | }
583 | }
584 | })->generate();
585 |
586 | $factory->addLoop($data);
587 |
588 | foreach ($data as $chunk) {
589 | $this->assertEquals(['a', 'b'], $chunk);
590 | }
591 | }
592 |
593 | public function testAddingUncountableLoop()
594 | {
595 | $factory = $this->getFactory();
596 |
597 | $factory->addLoop('');
598 |
599 | $expectedLoop = [
600 | 'iteration' => 0,
601 | 'index' => 0,
602 | 'remaining' => null,
603 | 'count' => null,
604 | 'first' => true,
605 | 'last' => null,
606 | 'odd' => false,
607 | 'even' => true,
608 | 'depth' => 1,
609 | 'parent' => null,
610 | ];
611 |
612 | $this->assertEquals([$expectedLoop], $factory->getLoopStack());
613 | }
614 |
615 | public function testIncrementingLoopIndices()
616 | {
617 | $factory = $this->getFactory();
618 |
619 | $factory->addLoop([1, 2, 3, 4]);
620 |
621 | $factory->incrementLoopIndices();
622 |
623 | $this->assertEquals(1, $factory->getLoopStack()[0]['iteration']);
624 | $this->assertEquals(0, $factory->getLoopStack()[0]['index']);
625 | $this->assertEquals(3, $factory->getLoopStack()[0]['remaining']);
626 | $this->assertTrue($factory->getLoopStack()[0]['odd']);
627 | $this->assertFalse($factory->getLoopStack()[0]['even']);
628 |
629 | $factory->incrementLoopIndices();
630 |
631 | $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']);
632 | $this->assertEquals(1, $factory->getLoopStack()[0]['index']);
633 | $this->assertEquals(2, $factory->getLoopStack()[0]['remaining']);
634 | $this->assertFalse($factory->getLoopStack()[0]['odd']);
635 | $this->assertTrue($factory->getLoopStack()[0]['even']);
636 | }
637 |
638 | public function testReachingEndOfLoop()
639 | {
640 | $factory = $this->getFactory();
641 |
642 | $factory->addLoop([1, 2]);
643 |
644 | $factory->incrementLoopIndices();
645 |
646 | $factory->incrementLoopIndices();
647 |
648 | $this->assertTrue($factory->getLoopStack()[0]['last']);
649 | }
650 |
651 | public function testIncrementingLoopIndicesOfUncountable()
652 | {
653 | $factory = $this->getFactory();
654 |
655 | $factory->addLoop('');
656 |
657 | $factory->incrementLoopIndices();
658 |
659 | $factory->incrementLoopIndices();
660 |
661 | $this->assertEquals(2, $factory->getLoopStack()[0]['iteration']);
662 | $this->assertEquals(1, $factory->getLoopStack()[0]['index']);
663 | $this->assertFalse($factory->getLoopStack()[0]['first']);
664 | $this->assertNull($factory->getLoopStack()[0]['remaining']);
665 | $this->assertNull($factory->getLoopStack()[0]['last']);
666 | }
667 |
668 | public function testMacro()
669 | {
670 | $factory = $this->getFactory();
671 | $factory->macro('getFoo', function () {
672 | return 'Hello World';
673 | });
674 | $this->assertEquals('Hello World', $factory->getFoo());
675 | }
676 |
677 | protected function getFactory()
678 | {
679 | return new Factory(
680 | m::mock(EngineResolver::class),
681 | m::mock(ViewFinderInterface::class),
682 | m::mock(DispatcherContract::class)
683 | );
684 | }
685 |
686 | protected function getFactoryArgs()
687 | {
688 | return [
689 | m::mock(EngineResolver::class),
690 | m::mock(ViewFinderInterface::class),
691 | m::mock(DispatcherContract::class),
692 | ];
693 | }
694 | }
695 |
--------------------------------------------------------------------------------