Skip to content

Commit 1c99b47

Browse files
lukeraymonddowningtaylorotwell
authored andcommitted
Adds fixes for stringable implementation.
1 parent 0613654 commit 1c99b47

File tree

3 files changed

+86
-44
lines changed

3 files changed

+86
-44
lines changed

src/Illuminate/View/Compilers/BladeCompiler.php

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,6 @@ class BladeCompiler extends Compiler implements CompilerInterface
102102
*/
103103
protected $echoFormat = 'e(%s)';
104104

105-
/**
106-
* Custom rendering callbacks for stringable objects.
107-
*
108-
* @var array
109-
*/
110-
public $echoHandlers = [];
111-
112105
/**
113106
* Array of footer lines to be added to the template.
114107
*
@@ -263,6 +256,13 @@ public function compileString($value)
263256
$result = $this->addFooters($result);
264257
}
265258

259+
// If there are blade echo handlers defined, we will prepend the file
260+
// with a resolved instance of the blade compiler, stored inside a
261+
// variable, so that it only has to be resolved a single time.
262+
if (!empty($this->echoHandlers)) {
263+
$result = $this->addBladeCompilerVariable($result);
264+
}
265+
266266
return str_replace(
267267
['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'],
268268
'',
@@ -711,22 +711,6 @@ public function getCustomDirectives()
711711
return $this->customDirectives;
712712
}
713713

714-
/**
715-
* Add a handler to be executed before echoing a given class.
716-
*
717-
* @param string|callable $class
718-
* @param callable|null $handler
719-
* @return void
720-
*/
721-
public function stringable($class, $handler = null)
722-
{
723-
if ($class instanceof Closure) {
724-
[$class, $handler] = [$this->firstClosureParameterType($class), $class];
725-
}
726-
727-
$this->echoHandlers[$class] = $handler;
728-
}
729-
730714
/**
731715
* Register a new precompiler.
732716
*

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

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,34 @@
22

33
namespace Illuminate\View\Compilers\Concerns;
44

5+
use Closure;
6+
use Illuminate\Support\Str;
7+
58
trait CompilesEchos
69
{
10+
/**
11+
* Custom rendering callbacks for stringable objects.
12+
*
13+
* @var array
14+
*/
15+
protected $echoHandlers = [];
16+
17+
/**
18+
* Add a handler to be executed before echoing a given class.
19+
*
20+
* @param string|callable $class
21+
* @param callable|null $handler
22+
* @return void
23+
*/
24+
public function stringable($class, $handler = null)
25+
{
26+
if ($class instanceof Closure) {
27+
[$class, $handler] = [$this->firstClosureParameterType($class), $class];
28+
}
29+
30+
$this->echoHandlers[$class] = $handler;
31+
}
32+
733
/**
834
* Compile Blade echos into valid PHP.
935
*
@@ -48,7 +74,7 @@ protected function compileRawEchos($value)
4874

4975
return $matches[1]
5076
? substr($matches[0], 1)
51-
: "<?php echo {$this->applyEchoHandlerFor($matches[2])}; ?>{$whitespace}";
77+
: "<?php echo {$this->wrapInEchoHandler($matches[2])}; ?>{$whitespace}";
5278
};
5379

5480
return preg_replace_callback($pattern, $callback, $value);
@@ -67,7 +93,7 @@ protected function compileRegularEchos($value)
6793
$callback = function ($matches) {
6894
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
6995

70-
$wrapped = sprintf($this->echoFormat, $this->applyEchoHandlerFor($matches[2]));
96+
$wrapped = sprintf($this->echoFormat, $this->wrapInEchoHandler($matches[2]));
7197

7298
return $matches[1] ? substr($matches[0], 1) : "<?php echo {$wrapped}; ?>{$whitespace}";
7399
};
@@ -90,22 +116,46 @@ protected function compileEscapedEchos($value)
90116

91117
return $matches[1]
92118
? $matches[0]
93-
: "<?php echo e({$this->applyEchoHandlerFor($matches[2])}); ?>{$whitespace}";
119+
: "<?php echo e({$this->wrapInEchoHandler($matches[2])}); ?>{$whitespace}";
94120
};
95121

96122
return preg_replace_callback($pattern, $callback, $value);
97123
}
98124

125+
/**
126+
* Add an instance of the blade echo handler to the start of the compiled string.
127+
*
128+
* @param string $result
129+
* @return string
130+
*/
131+
protected function addBladeCompilerVariable($result)
132+
{
133+
return "<?php \$__bladeCompiler = app('blade.compiler'); ?>" . $result;
134+
}
135+
99136
/**
100137
* Wrap the echoable value in an echo handler if applicable.
101138
*
102139
* @param string $value
103140
* @return string
104141
*/
105-
protected function applyEchoHandlerFor($value)
142+
protected function wrapInEchoHandler($value)
106143
{
107-
return empty($this->echoHandlers)
108-
? $value
109-
: "is_object($value) && isset(app('blade.compiler')->echoHandlers[get_class($value)]) ? call_user_func_array(app('blade.compiler')->echoHandlers[get_class($value)], [$value]) : $value";
144+
return empty($this->echoHandlers) ? $value : '$__bladeCompiler->applyEchoHandler(' . Str::beforeLast($value, ';') . ')';
145+
}
146+
147+
/**
148+
* Apply the echo handler for the value if it exists.
149+
*
150+
* @param $value string
151+
* @return string
152+
*/
153+
public function applyEchoHandler($value)
154+
{
155+
if (is_object($value) && isset($this->echoHandlers[get_class($value)])) {
156+
return call_user_func($this->echoHandlers[get_class($value)], $value);
157+
}
158+
159+
return $value;
110160
}
111161
}

tests/View/Blade/BladeEchoHandlerTest.php

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,34 @@ protected function setUp(): void
1717
});
1818
}
1919

20-
public function testBladeHandlersCanBeAddedForAGivenClass()
21-
{
22-
$this->assertSame('Hello World', $this->compiler->echoHandlers[Fluent::class](new Fluent()));
23-
}
24-
2520
public function testBladeHandlerCanInterceptRegularEchos()
2621
{
2722
$this->assertSame(
28-
"<?php echo e(is_object(\$exampleObject) && isset(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)]) ? call_user_func_array(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)], [\$exampleObject]) : \$exampleObject); ?>",
23+
"<?php \$__bladeCompiler = app('blade.compiler'); ?><?php echo e(\$__bladeCompiler->applyEchoHandler(\$exampleObject)); ?>",
2924
$this->compiler->compileString('{{$exampleObject}}')
3025
);
3126
}
3227

3328
public function testBladeHandlerCanInterceptRawEchos()
3429
{
3530
$this->assertSame(
36-
"<?php echo is_object(\$exampleObject) && isset(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)]) ? call_user_func_array(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)], [\$exampleObject]) : \$exampleObject; ?>",
31+
"<?php \$__bladeCompiler = app('blade.compiler'); ?><?php echo \$__bladeCompiler->applyEchoHandler(\$exampleObject); ?>",
3732
$this->compiler->compileString('{!!$exampleObject!!}')
3833
);
3934
}
4035

4136
public function testBladeHandlerCanInterceptEscapedEchos()
4237
{
4338
$this->assertSame(
44-
"<?php echo e(is_object(\$exampleObject) && isset(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)]) ? call_user_func_array(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)], [\$exampleObject]) : \$exampleObject); ?>",
39+
"<?php \$__bladeCompiler = app('blade.compiler'); ?><?php echo e(\$__bladeCompiler->applyEchoHandler(\$exampleObject)); ?>",
4540
$this->compiler->compileString('{{{$exampleObject}}}')
4641
);
4742
}
4843

4944
public function testWhitespaceIsPreservedCorrectly()
5045
{
5146
$this->assertSame(
52-
"<?php echo e(is_object(\$exampleObject) && isset(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)]) ? call_user_func_array(app('blade.compiler')->echoHandlers[get_class(\$exampleObject)], [\$exampleObject]) : \$exampleObject); ?>\n\n",
47+
"<?php \$__bladeCompiler = app('blade.compiler'); ?><?php echo e(\$__bladeCompiler->applyEchoHandler(\$exampleObject)); ?>\n\n",
5348
$this->compiler->compileString("{{\$exampleObject}}\n")
5449
);
5550
}
@@ -68,10 +63,23 @@ public function testHandlerLogicWorksCorrectly()
6863

6964
$exampleObject = new Fluent();
7065

71-
eval(
72-
Str::of($this->compiler->compileString('{{$exampleObject}}'))
73-
->after('<?php')
74-
->beforeLast('?>')
75-
);
66+
eval(Str::of($this->compiler->compileString('{{$exampleObject}}'))->remove(['<?php', '?>']));
67+
}
68+
69+
public function testHandlerLogicWorksCorrectlyWithSemicolon()
70+
{
71+
$this->expectExceptionMessage('The fluent object has been successfully handled!');
72+
73+
$this->compiler->stringable(Fluent::class, function ($object) {
74+
throw new Exception('The fluent object has been successfully handled!');
75+
});
76+
77+
app()->singleton('blade.compiler', function () {
78+
return $this->compiler;
79+
});
80+
81+
$exampleObject = new Fluent();
82+
83+
eval(Str::of($this->compiler->compileString('{{$exampleObject;}}'))->remove(['<?php', '?>']));
7684
}
7785
}

0 commit comments

Comments
 (0)