Custom Blade directives is an overkill

Mike Sirius
3 min readFeb 9, 2021

--

The current app I’m building is a FinTech project and requires an accessive use of currency and decimal number formatting both in business logic and presentation.

One of the examples is a Compact Number Format, instead of showing a long integer we want to compact it to a short integer with K, M, B, T padding.

<?php$fmt = new NumberFormatter('en_US', NumberFormatter::PADDING_POSITION);for ($i = 1; $i < 1.E10; $i *= 10)
echo $i .' => '. $fmt->format($i) . PHP_EOL;
* * *Output:1 => 1
10 => 10
100 => 100
1000 => 1K
10000 => 10K
100000 => 100K
1000000 => 1M
10000000 => 10M
100000000 => 100M
1000000000 => 1B

This feels like a great use-case for a custom Blade directive.

<td class="">
@compactNumber($quote->marketCap)
</td>

Looks great, let’s get that done.

The $expression of Blade directive is a literal string

If you never created a custom Blade directive before, you will bet frustrated. They are confusing.

Why is that?

The purpose of a Blade directive is to return a PHP-string that can be inserted into the cached view file, meaning it is not possible to directly interact with data within the blade component itself.

We are creating a quick test inside AppServiceProvider.

public function boot()
{
Blade::directive('compactNumber', function ($expression) {

// What do you think the output will be?
dd($expression);
});
}

An expected answer would be—the value of $quote->marketCap—it’s not, it will be a literal string of "$quote->marketCap".

You see, Blade directive only receives a string $expression of the directive variable, not the variable itself. We need to remember we are building a PHP-string line which will be evaluated when the view is rendered.

As a useful trick, get your code working inside the view with PHP-directive:

# In the view.
@php
$fmt = new \NumberFormatter('en_US', \NumberFormatter::PADDING_POSITION);
$fmt->setPattern('0.###');
echo $fmt->format($quote->marketCap);
@endphp

Now we know that our directive needs to return the below as PHP-string.

$fmt = new \NumberFormatter('en_US', \NumberFormatter::PADDING_POSITION);
$fmt->setPattern('0.###');
echo $fmt->format($quote->marketCap);

Let’s update the directive.

Blade::directive('compactNumber', function ($expression) {

return '<?php
$fmt = new \NumberFormatter(\'en_US\', \NumberFormatter::PADDING_POSITION);
$fmt->setPattern(\'0.###\');

echo $fmt->format('. $expression .');
?>';
});

That’s not a great look, a code which is hard to read and understand. It gets even more complicated if we add secondary parameters to the Blade directive.

Let’s say we want to control the pattern via Blade directive.

<td class="">
@compactNumber($quote->marketCap, 0.###)
</td>

As Blade directive $expression is a literal string, we’ll need to parse the parameters out and then use them to construct the end-result PHP-string.

Blade::directive('compactNumber', function ($expression) {    // $expression is "$quote->marketCap, 0.###"    $params = explode(', ', str_replace(['(', ')'], '', $expression));

return '<?php
$fmt = new \NumberFormatter(\'en_US\', \NumberFormatter::PADDING_POSITION);
$fmt->setPattern(\''. $params[1] .'\');

echo $fmt->format('. $params[0] .');
?>';
});

I like this even less. Of course, we could pretty things up a bit, however, considering we need to do php artisan view:clear every time we make a change, all the experience feels like an overkill.

Is there a better way?

Of course, it’s Laravel—a better way is the name.

Create a service and port the logic there, then inject it into the view and use it.

# In the view.
@inject('number', 'App\Services\NumberFormat')
<td class="">
{{ $number->compact($quote->marketCap) }}
</td>

Much cleaner and allows the code to be utilised everywhere in the application.

So, a total NO to custom Blade directives?

The underestimated difficulty of putting it together, ugly looking code, hard to maintain and expand it… yeah, a no. Use built-in Laravel directives and avoid solving your problems with custom ones.

There are useful one-liners for debugging and other small tasks which can be found here. They have their use, I would still avoid them, though.

--

--

Mike Sirius
Mike Sirius

Written by Mike Sirius

Tech growth strategist with 25+ years in founding and scaling startups. Host of the "Mastering Tech Growth" podcast. Sharing my and industry leaders' insights.

Responses (1)