Skip to content

Commit 0c5ca22

Browse files
committed
feat: introduce @maybe directive
1 parent e5e00b6 commit 0c5ca22

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\View\Compilers\Concerns;
44

5+
use Illuminate\Contracts\View\ViewCompilationException;
56
use Illuminate\Support\Str;
67

78
trait CompilesConditionals
@@ -417,4 +418,25 @@ protected function compileEndPushIf()
417418
{
418419
return '<?php $__env->stopPush(); endif; ?>';
419420
}
421+
422+
/**
423+
* Compile conditional HTML attributes into valid PHP.
424+
*
425+
* @param string $expression
426+
* @return string
427+
*
428+
* @throws \Illuminate\Contracts\View\ViewCompilationException
429+
*/
430+
protected function compileMaybe($expression)
431+
{
432+
$parts = explode(',', $this->stripParentheses($expression), 2);
433+
434+
if (2 !== count($parts)) {
435+
throw new ViewCompilationException('The @maybe directive requires exactly 2 parameters.');
436+
}
437+
438+
[ $attribute, $data ] = array_map('trim', $parts);
439+
440+
return "<?php if($data !== '' && $data !== null && trim(is_bool($data) ? ($data ? 'true' : 'false') : $data) !== '') echo $attribute . '=\"' . e(is_bool($data) ? ($data ? 'true' : 'false') : $data) . '\"'; ?>";
441+
}
420442
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\View\Blade;
4+
5+
use Illuminate\Contracts\View\ViewCompilationException;
6+
7+
class BladeMaybeStatementsTest extends AbstractBladeTestCase
8+
{
9+
public function testMaybeWithMissingParameter()
10+
{
11+
$this->expectException(ViewCompilationException::class);
12+
$this->expectExceptionMessage('The @maybe directive requires exactly 2 parameters.');
13+
14+
$string = "@maybe('title')";
15+
$this->compiler->compileString($string);
16+
}
17+
18+
public function testMaybeWithSimpleVariable()
19+
{
20+
$string = '<a @maybe(\'title\', $title)>Link</a>';
21+
$expected = '<a <?php if($title !== \'\' && $title !== null && trim(is_bool($title) ? ($title ? \'true\' : \'false\') : $title) !== \'\') echo \'title\' . \'="\' . e(is_bool($title) ? ($title ? \'true\' : \'false\') : $title) . \'"\'; ?>>Link</a>';
22+
23+
$this->assertEquals($expected, $this->compiler->compileString($string));
24+
}
25+
26+
public function testMaybeWithObjectProperty()
27+
{
28+
$string = '<a @maybe(\'title\', $link->title)>Link</a>';
29+
$expected = '<a <?php if($link->title !== \'\' && $link->title !== null && trim(is_bool($link->title) ? ($link->title ? \'true\' : \'false\') : $link->title) !== \'\') echo \'title\' . \'="\' . e(is_bool($link->title) ? ($link->title ? \'true\' : \'false\') : $link->title) . \'"\'; ?>>Link</a>';
30+
31+
$this->assertEquals($expected, $this->compiler->compileString($string));
32+
}
33+
34+
public function testMaybeWithArrayAccess()
35+
{
36+
$string = '<a @maybe(\'title\', $data[\'title\'])>Link</a>';
37+
$expected = '<a <?php if($data[\'title\'] !== \'\' && $data[\'title\'] !== null && trim(is_bool($data[\'title\']) ? ($data[\'title\'] ? \'true\' : \'false\') : $data[\'title\']) !== \'\') echo \'title\' . \'="\' . e(is_bool($data[\'title\']) ? ($data[\'title\'] ? \'true\' : \'false\') : $data[\'title\']) . \'"\'; ?>>Link</a>';
38+
39+
$this->assertEquals($expected, $this->compiler->compileString($string));
40+
}
41+
42+
public function testMaybeWithMultipleAttributes()
43+
{
44+
$string = '<img @maybe(\'alt\', $alt) @maybe(\'data-src\', $src)/>';
45+
$expected = '<img <?php if($alt !== \'\' && $alt !== null && trim(is_bool($alt) ? ($alt ? \'true\' : \'false\') : $alt) !== \'\') echo \'alt\' . \'="\' . e(is_bool($alt) ? ($alt ? \'true\' : \'false\') : $alt) . \'"\'; ?> <?php if($src !== \'\' && $src !== null && trim(is_bool($src) ? ($src ? \'true\' : \'false\') : $src) !== \'\') echo \'data-src\' . \'="\' . e(is_bool($src) ? ($src ? \'true\' : \'false\') : $src) . \'"\'; ?>/>';
46+
47+
$this->assertEquals($expected, $this->compiler->compileString($string));
48+
}
49+
50+
public function testMaybeWithSpacesAroundParameters()
51+
{
52+
$string = '<a @maybe( \'title\' , $title )>Link</a>';
53+
$expected = '<a <?php if($title !== \'\' && $title !== null && trim(is_bool($title) ? ($title ? \'true\' : \'false\') : $title) !== \'\') echo \'title\' . \'="\' . e(is_bool($title) ? ($title ? \'true\' : \'false\') : $title) . \'"\'; ?>>Link</a>';
54+
55+
$this->assertEquals($expected, $this->compiler->compileString($string));
56+
}
57+
58+
public function testMaybeWithNonEmptyString()
59+
{
60+
$this->assertSame(
61+
"<?php if(\$title !== '' && \$title !== null && trim(is_bool(\$title) ? (\$title ? 'true' : 'false') : \$title) !== '') echo 'title' . '=\"' . e(is_bool(\$title) ? (\$title ? 'true' : 'false') : \$title) . '\"'; ?>",
62+
$this->compiler->compileString("@maybe('title', \$title)")
63+
);
64+
}
65+
66+
public function testMaybeWithEmptyString()
67+
{
68+
$string = "@maybe('title', \$title)";
69+
$compiled = $this->compiler->compileString($string);
70+
71+
$this->assertSame('', $this->evaluateBlade($compiled, ['title' => '']));
72+
}
73+
74+
public function testMaybeWithNull()
75+
{
76+
$string = "@maybe('title', \$title)";
77+
$compiled = $this->compiler->compileString($string);
78+
79+
$this->assertSame('', $this->evaluateBlade($compiled, ['title' => null]));
80+
}
81+
82+
public function testMaybeWithZero()
83+
{
84+
$string = "@maybe('data-count', \$count)";
85+
$compiled = $this->compiler->compileString($string);
86+
87+
$this->assertSame('data-count="0"', $this->evaluateBlade($compiled, ['count' => 0]));
88+
}
89+
90+
public function testMaybeWithFalse()
91+
{
92+
$string = "@maybe('data-active', \$active)";
93+
$compiled = $this->compiler->compileString($string);
94+
95+
$this->assertSame('data-active="false"', $this->evaluateBlade($compiled, ['active' => false]));
96+
}
97+
98+
public function testMaybeWithTrue()
99+
{
100+
$string = "@maybe('data-active', \$active)";
101+
$compiled = $this->compiler->compileString($string);
102+
103+
$this->assertSame('data-active="true"', $this->evaluateBlade($compiled, ['active' => true]));
104+
}
105+
106+
public function testMaybeWithValidString()
107+
{
108+
$string = "@maybe('title', \$title)";
109+
$compiled = $this->compiler->compileString($string);
110+
111+
$this->assertSame('title="You can just do things"', $this->evaluateBlade($compiled, ['title' => 'You can just do things']));
112+
}
113+
114+
public function testMaybeEscapesHtmlEntities()
115+
{
116+
$string = "@maybe('title', \$title)";
117+
$compiled = $this->compiler->compileString($string);
118+
119+
$this->assertSame('title="&lt;script&gt;alert(&#039;xss&#039;)&lt;/script&gt;"',
120+
$this->evaluateBlade($compiled, ['title' => "<script>alert('xss')</script>"]));
121+
}
122+
123+
public function testMaybeWithWhitespaceOnlyString()
124+
{
125+
$string = "@maybe('title', \$title)";
126+
$compiled = $this->compiler->compileString($string);
127+
128+
// Whitespace-only strings are considered empty.
129+
$this->assertSame('', $this->evaluateBlade($compiled, ['title' => ' ']));
130+
}
131+
132+
public function testMaybeWithNumericString()
133+
{
134+
$string = "@maybe('data-id', \$id)";
135+
$compiled = $this->compiler->compileString($string);
136+
137+
$this->assertSame('data-id="123"', $this->evaluateBlade($compiled, ['id' => '123']));
138+
}
139+
140+
public function testMaybeWithInt()
141+
{
142+
$string = "@maybe('data-id', \$id)";
143+
$compiled = $this->compiler->compileString($string);
144+
145+
$this->assertSame('data-id="123"', $this->evaluateBlade($compiled, ['id' => 123]));
146+
}
147+
148+
public function testMaybeInHtmlContext()
149+
{
150+
$string = '<a href="#" @maybe(\'title\', $title)>Link</a>';
151+
$compiled = $this->compiler->compileString($string);
152+
153+
$this->assertSame('<a href="#" title="click">Link</a>',
154+
$this->evaluateBlade($compiled, ['title' => 'click']));
155+
156+
$this->assertSame('<a href="#" >Link</a>',
157+
$this->evaluateBlade($compiled, ['title' => '']));
158+
}
159+
160+
protected function evaluateBlade(string $compiled, array $data = []): string
161+
{
162+
extract($data);
163+
ob_start();
164+
eval('?>' . $compiled);
165+
return ob_get_clean();
166+
}
167+
}

0 commit comments

Comments
 (0)