Keep your templates short and tidy with Laravel Blade custom conditional directives
Tags: laravel
Published: 2020-07-03 17:10:08

The [Laravel Blade](https://laravel.com/docs/7.x/blade) templating engine provides us with a number of conditional directives, namely `@if`, `@unless`, `@isset`, `@empty`, and even directives for checking common use cases such as user authentication and  environment name.

Some examples: 

```php
@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif
```

```php
@auth
    // The user is authenticated...
@endauth

@guest
    // The user is not authenticated...
@endguest
```

See the complete list of Blade directives in the [official documentation](https://laravel.com/docs/7.x/blade).

## Problem

Useful as they are, we may need custom conditionals not covered by the directives provided out of the box.

Some scenarios where we may end up with long conditionals:
- Although internationalisation and localisation are ideally managed outside view templates, we may need conditionals based on the active language, for instance to add CSS styles.
- Our website/app is published to several markets with slightly different requirements but otherwise share the same codebase. Thus we may deal with environments like `market-a-production`, `market-a-staging`, `market-b-production`, `market-b-staging`.
  - The env names might even be irregular, for instance `production`, `staging`, `market-b-production`, and `market-b-staging`.
- ... and other product-specific cases

When we need to write this kind of conditional multiple times, it makes our template files messy and increases typo risk.

## Solution

Enter the `Blade::if` method. We can make as many as we want in the `boot` method of our `AppServiceProvider`.

Example from the [official documentation](https://laravel.com/docs/7.x/blade#custom-if-statements):

```php
// app/Providers/AppServiceProvider.php

use Illuminate\Support\Facades\Blade;

class AppServiceProvider extends ServiceProvider
{
  public function boot() {
    // Start custom conditional
    Blade::if('cloud', function ($provider) {
      return config('filesystems.default') === $provider;
    });
    // end
  }
}
```

- The custom conditional directive is called `cloud` (we can use any name we want).
- The function takes the passed value as argument. If the `$provider` value passed in the template equals the value of `config('filesystems.default')`, it returns `true`.

Then we can use it like this.

```php
// resources/views/your_template_file.blade.php
@cloud('digitalocean')
    // The application is using the digitalocean cloud provider...
@elsecloud('aws')
    // The application is using the aws provider...
@else
    // The application is not using the digitalocean or aws environment...
@endcloud
```

### More examples

Say our website is available in multiple languages, both _left-to-right_ (such as English) and _right-to-left_ (such as Arabic and Hebrew). We may need to apply different CSS styles or markup based on the language direction. We can configure a custom conditional to accommodate this.

```php
// app/Providers/AppServiceProvider.php

$RTL_LANG = ['ar', 'he']; // One place to modify if we add more RTL languages

Blade::if('langrtl', function () {
  return in_array($app()->getLocale(), $RTL_LANG) ? true : false;
});
```

```php
// resources/views/your_template_file.blade.php

// BEFORE: Long conditional 😫
@if (app()->getLocale() === 'ar' || app()->getLocale() === 'he')
  // RTL language stuff...
@endif

// AFTER: More concise 😃
@langrtl()
  // RTL language stuff...
@endlangrtl
```

As another example, we add a conditional that targets multiple environments. “Market A” website uses the environments `production` and `staging`, while “Market B” website uses `market-b-production` and `market-b-staging`.

```php
// app/Providers/AppServiceProvider.php
Blade::if('envmarket', function ($market) {
  if ($market === 'a') {
    return $this->app->environment() === 'staging' || $this->app->environment() === 'production';
  } elseif ($market === 'b') {
    return $this->app->environment() === 'market-b-staging' || $this->app->environment() === 'market-b-production';
  }
});
```

```php
// resources/views/your_template_file.blade.php

// BEFORE: Long conditional 😫
@if (in_array(App::environment(), ['staging', 'production']))
  // Market A stuff...
@if (in_array(App::environment(), ['market-b-staging', 'market-b-production']))
  // Market B stuff...
@endif

// AFTER: More concise 😃
@envmarket('a')
  // Market A stuff...
@elseenvmarket('b')
  // Market B stuff...
@endenvmarket
```

## Conclusion

Ideally, we should have as little logic as possible in our view templates and delegate it to controllers, services, and helper functions instead. In reality, it's not always 100% possible and we may need to add custom conditionals to our view template files. The `Blade::if` method helps us abstract away frequently-used custom conditionals, and thus keeping our template files tidy.