I used Blade::extend()
to create a more complex directive than it is possible with Blade::directive()
. After trying different approaches for hours, this is what I ended up with—and it works. Except for one thing, I'm still not entirely fine with:
When I pass $matches['callback']
(which contains ordinary blade syntax) to the output, Blade converts a string like this:
<a href="{{ route('user', [ 'user' => $user ]) }}">{{ $user->username }}</a>
to
<a href="<?php echo e(route('community.users.user', [ 'user' => $author->id ])); ?>"><?php echo e($author->username); ?></a>
This is done, because internally, Blade seems to use Blade::compileString()
. That might be fine in some cases, but as you can probably see, it breaks the layout, because the whole <a />
tag has to be echo
'd and the inner stuff has to be concatenated like this to return valid HTML:
'<a href="'. route('community.users.user', [ 'user' => $author->id ]) .'">'. $author->username .'</a>'
As you can see, I have a workaround, to manually replace {{
and }}
so that string concatenation is happening, but I wonder if there's a way, to do that properly.
AppServiceProvider.php:
Blade::extend(function($expression) {
$pattern = '/\@mapJoin\((?<collection>.*?), \'(?<variable>.*?)\', (?<delimiter>.*?)\)(?<callback>.*?)\@endMapJoin/ms';
return preg_replace_callback($pattern, function($matches) {
// @todo dynamic compiling
$callback = preg_replace('/\{\{(.*?)\}\}/', "'.$1.'", trim($matches["callback"]));
return '<?php echo '.$matches['collection'].'->map(function($'.$matches['variable'].') {
return \''.$callback.'\';
})->join('.$matches["delimiter"].'); ?>'; //Blade::compileString()
}, $expression);
});
I wanted to have something in my blade syntax which allows me to do the following:
@mapJoin($story->authors, 'author', ',')
<!-- normal markup and blade stuff -->
@endMapJoin
for example:
@mapJoin($story->authors, 'author', ',')
<a href="{{ route('community.users.user', [ 'user' => $author->id ]) }}">{{ $author->username }}</a>
@endMapJoin
to turn it into:
$collection->map(function($variable) {
return echo $callback;
})->join($delimiter);
which would result in:
<a href="community/users/user/1">User 1</a>, <a href="community/users/user/2">User 2</a>, <a href="community/users/user/1">User 2</a>
Blade::directive()
sadly only replaces the @something
itself, not the content between, and it does not have a concept of start and end tag. I tried to address that with my solution using Blade::extend()
. Even more general, it would be something like this:
use Illuminate\Support\Facades\Blade;
use Illuminate\View\Compilers\BladeCompiler as CompilersBladeCompiler;
use Illuminate\Support\Str;
class BladeCompiler extends CompilersBladeCompiler
{
public static function compileDirective(string $name, array $parameters, callable $callback)
{
$paramString = '';
if (count($parameters)) {
$paramString = '\(';
foreach($parameters as $parameter) {
$paramString .= '(?<'.$parameter.'>.*?)';
}
$paramString .= '\)';
}
return Blade::extend(function($expression) use($name, $paramString, $callback) {
$pattern = '/\@'.$name.$paramString.'(?<content>.*?)\@end'.Str::studly($name).'/ms';
return preg_replace_callback($pattern, $callback, $expression);
});
}
}
My Answer:
This is a complex issue and it seems like you have put a lot of effort into finding a solution. One possible approach to handle this issue could be to create a custom Blade directive that processes the content between the start and end tags in a way that suits your needs.
Here is an example of how you could create a custom Blade directive to achieve the desired output:
php
BladeCompiler::compileDirective('mapJoin', ['collection', 'variable', 'delimiter'], function($matches) {
$callback = preg_replace('/\{\{(.*?)\}\}/', '.$1.', trim($matches['content']));
return 'map(function($'.$matches['variable'].') {
return '.$callback.';
})->join('.$matches['delimiter'].'); ?>';
});
With this custom directive, you can use the following syntax in your Blade templates:
blade
@mapJoin($story->authors, 'author', ',')
{{ $author->username }}
@endMapJoin
This should output the desired result:
html
User 1, User 2, User 2
By creating a custom Blade directive, you have more control over how the content between the start and end tags is processed, allowing you to achieve the desired string concatenation output.
Rate this post
4 of 5 based on 4927 votesComments