Zerillian Engineering

Irradiated with cheeky programmer fervor!

Blade Extensions in Laravel

| Comments

Updated for Laravel 4 - Click Here

This one took me a little while to figure out, since it’s not particularly well documented. In Laravel, it’s possible to extend the syntax of the Blade template language with your own statements. I heard it mentioned once, and found a few functions in the API documentation which led me to creating customized Blade statements.

It’s Not That Difficult.

Blade has a variety of built-in statements to manage the logic and rendering of your templates. At its core, Blade simply translates these statements into regular PHP. For example:

@if(Session::has(‘login-errors’))
<div class=”alert”><h2>Uh-oh! {{ Session::get(‘login-errors’) }} </h2></div>
@endif

This set of statements looks in the Session for any login errors and displays them. Here, we used Blade’s {{ }} syntax for echoing statements, and the @if and @endif statements to make a decision. Once touched by Blade, it looks like this:

<?php if(Session::has(‘login-errors’)): ?>
<div class=”alert”><h2>Uh-oh! <?php echo Session::get(‘login-errors’) ?></h2></div>
<?php endif; ?>

There’s nothing too special about it now. It’s plain, regularly parseable PHP. Adding a Blade extension only means adding a few such substitutions yourself.

To register a Blade extension, you do this:

Blade::extend(function($view) {
    // Statement code...
});

Blade::extend() adds a closure function onto a stack of functions through which a view is run during the rendering process. All of the typical Blade statements are defined in a similar manner. The view is examined and all instances of @if are replaced with <?php if(): ?>, instances of @render() are replaced with <?php echo render() ?>, etc.

The $view variable will contain a plain text version of the view to be rendered, and the closure defined will perform the replacement of the custom Blade syntax with PHP statements. Both of these are handled as strings.

Markup Extensions

You could add custom Blade statements as a compact syntax for elements you frequently use. Let’s say you use a lot of Zurb Foundation’s progress bar components. You’d register the Blade extension like this:

Blade::extend(function ($view) {
$html = "<div class=\"progress\"><span class=\"meter\"></span></div>";
return str_replace("@progressbar", $html, $view);
});

And now, @progressbar becomes…

<div class=”progress”><span class=”meter”></span></div>

Although that’s not particularly useful, since we can’t set the progress of the progress bar. Let’s tweak that Blade extension.

Blade::extend(function ($view) {
$html = "<div class=\"progress\"><span class=\"meter\" style=\"width: $1%\"></span></div>";
return preg_replace("#\s*@progressbar\(\s*([0-9]*)\s*\)#", $html, $view);
});

So if we use these:

<div class="row">
    <div class="small-12 columns">
        @progressbar(75)
        @progressbar(10)
        @progressbar(43)
        @progressbar(100)
    </div>
</div>

We end up with this:

It’s now possible to specify a percentage which the progress bar should represent. The closure extracts the number from our custom statement and uses it to set the CSS width property of the “meter” class element.

If you’re not too familiar with regular expressions, read up a bit on Wikipedia. A basic understanding isn’t too difficult to attain, and it’s logically straightforward to learn their proper use. The most important part is that this expression…

\s*@progressbar\(\s*([0-9]*)\s*\)

…extracts the numbers as a sub-expression. These numbers are inserted into the $1 symbol in the replacement text.

Simple string manipulation functions like str_replace (docs) work well for one-off replacements like inserting static PHP code, but regex-capable functions such as preg_replace (docs) are necessary if you’re including some arguments or modifiers that require a RegEx to find and replace.

Functional Extensions

In this example, I’ll use the Laravel Browser bundle to identify mobile users, and add some Blade extensions to change the rendering of templates for them.

routes.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

IoC::singleton('Browser', function()
{
  return new Browser($_SERVER['HTTP_USER_AGENT']);
});

//       @mobile statement               ///////////////////////////////////////////
Blade::extend(function($view)
{
  return str_replace('@mobile', "<?php if(IoC::resolve('Browser')->isMobile()): ?>", $view);
});


//       @endmobile statement            ///////////////////////////////////////////
Blade::extend(function($view)
{
  return str_replace('@endmobile', "<?php endif; ?>", $view);
});


//       @desktop statement              ///////////////////////////////////////////
Blade::extend(function ($view) {
  return str_replace('@desktop', "<?php if(!IoC::resolve('Browser')->isMobile()): ?>", $view);
});


//       @enddesktop statement           ///////////////////////////////////////////
Blade::extend(function ($view) {
  return str_replace('@enddesktop', "<?php endif; ?>", $view);
});

// Your regular routes

The first operation registers an instance of the Browser class in the IoC container (docs). Browser is used to determine what user agent the client is running. In this case, it’s to distinguish users on desktop computers from users on phones and tablets. The IoC container handily keeps this instance ready for us. The code that follows registers the various Blade extensions with Laravel. There are four extensions illustrated here:

  • @mobile
  • @endmobile
  • @desktop
  • @enddesktop

The @desktop and @enddesktop statements will only render content between them if the user is on a desktop PC. This might be used to add sidebars or extra navigation material only on desktops, where there’s enough space to add such elements. @mobile and @endmobile do the same thing for mobile devices. These might be used to add sections of sites for mobile users, such as compact dashboards that are more suitable for phones and tablets. They also work with the @else Blade statement, since they’re really just PHP if statements. Nothing all that special about them.

I’ve mocked up a Blade template for a user’s dashboard on a food-tracking website. It’s a bit lengthy, but it’ll show you how to conditionally change content with Blade statements. In here, mobile users will see a condensed dashboard with large buttons, while desktop users will see a quick-add form, a search box, and a list of recent eats by friends. They also see different navigation components.

Using the mobile Blade extensions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE HTML>
<head>
  <title>An Absolutely Incredible Food App</title>
</head>
<body>
  @mobile
  <h1>Welcome to our mobile site.</h1>
  @endmobile
  @desktop
  <h1>Welcome to our site.</h1>
  @enddesktop
  
  @mobile
  <dl class="sub-nav">
    <dt>Filter:</dt>
    <dd><a href="#">All</a></dd>
    <dd><a href="#">Tasty</a></dd>
    <dd><a href="#">Yucky</a></dd>
    <dd><a href="#">Friends</a></dd>
  </dl>
  @endmobile
  
  @desktop
  <ul class="side-nav">
    <li><a href="#">All Foods</a></li>
    <li><a href="#">Tasty Foods</a></li>
    <li><a href="#">Yucky Foods</a></li>
    <li><a href="#">Friend's Foods</a></li>
  </ul>
  @enddesktop
  
  @mobile
  <h3>Mobile Dashboard</h3>
  <div class="buttons">
      <a href="/food/add">Add Something You Ate</a>
      <a href="/friends/foods">What Your Friends Ate</a>
      <a href="/list">Eat List</a>
      <a href="/messages">Messages</a>
  </div>
  @endmobile
  
  @desktop
  <h3>Your Dashboard</h3>
  <form>
      <input type="text" name="new_food" placeholder="Put in a new food..."/>
      <button type="submit">Add it!</button>
  </form>
  <form>
      <input type="text" name="search_foods" placeholder="Search for a food..."/>
      <button type="submit">Find it!</button>
  </form>
  
  <table>
      <thead>
          <tr>
              <th>Date</th>
              <th>Recent Food</th>
              <th>Eaten By</th>
              <th>YUMS and YUCKS</th>
          </tr>
      </thead>
      <tbody>
          <tr>
              <td>Just Now</td>
              <td>Pineapple Pizza</td>
              <td>J. Kyle</td>
              <td>5 YUMS and 13 YUCKS</td>
          </tr>
          <tr>
              <td>2 hours ago</td>
              <td>Breadsticks</td>
              <td>M. Toledo</td>
              <td>8 YUMS and 0 YUCKS</td>
          </tr>
          <tr>
              <td>3 hours ago</td>
              <td>Raw Eggs</td>
              <td>E. Ferguson</td>
              <td>0 YUMS and 20 YUCKS</td>
          </tr>
      </tbody>
  </table>
  @enddesktop
  
</body>
</html>

And after running through Blade and our custom extensions, that view looks like this:

After rendering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<!DOCTYPE HTML>
<head>
  <title>An Absolutely Incredible Food App</title>
</head>
<body>
  <?php if(IoC::resolve('Browser')->isMobile()): ?>
  <h1>Welcome to our mobile site.</h1>
  <?php endif; ?>
  <?php if(!IoC::resolve('Browser')->isMobile()): ?>
  <h1>Welcome to our site.</h1>
  <?php endif; ?>
  
  
  <?php if(IoC::resolve('Browser')->isMobile()): ?>
  <dl class="sub-nav">
    <dt>Filter:</dt>
    <dd><a href="#">All</a></dd>
    <dd><a href="#">Tasty</a></dd>
    <dd><a href="#">Yucky</a></dd>
    <dd><a href="#">Friends</a></dd>
  </dl>
  <?php endif; ?>
  
  <?php if(!IoC::resolve('Browser')->isMobile()): ?>
  <ul class="side-nav">
    <li><a href="#">All Foods</a></li>
    <li><a href="#">Tasty Foods</a></li>
    <li><a href="#">Yucky Foods</a></li>
    <li><a href="#">Friend's Foods</a></li>
  </ul>
  <?php endif; ?>
  
  <?php if(IoC::resolve('Browser')->isMobile()): ?>
  <h3>Mobile Dashboard</h3>
  <div class="buttons">
      <a href="/food/add">Add Something You Ate</a>
      <a href="/friends/foods">What Your Friends Ate</a>
      <a href="/list">Eat List</a>
      <a href="/messages">Messages</a>
  </div>
  <?php endif; ?>
  
  <?php if(!IoC::resolve('Browser')->isMobile()): ?>
  <h3>Your Dashboard</h3>
  <form>
      <input type="text" name="new_food" placeholder="Put in a new food..."/>
      <button type="submit">Add it!</button>
  </form>
  <form>
      <input type="text" name="search_foods" placeholder="Search for a food..."/>
      <button type="submit">Find it!</button>
  </form>
  
  <table>
      <thead>
          <tr>
              <th>Date</th>
              <th>Recent Food</th>
              <th>Eaten By</th>
              <th>YUMS and YUCKS</th>
          </tr>
      </thead>
      <tbody>
          <tr>
              <td>Just Now</td>
              <td>Pineapple Pizza</td>
              <td>J. Kyle</td>
              <td>5 YUMS and 13 YUCKS</td>
          </tr>
          <tr>
              <td>2 hours ago</td>
              <td>Breadsticks</td>
              <td>M. Toledo</td>
              <td>8 YUMS and 0 YUCKS</td>
          </tr>
          <tr>
              <td>3 hours ago</td>
              <td>Raw Eggs</td>
              <td>E. Ferguson</td>
              <td>0 YUMS and 20 YUCKS</td>
          </tr>
      </tbody>
  </table>
  <?php endif; ?>
  
</body>
</html>

When creating Blade extensions, you can use whatever string modification function you’d like as long as it returns $view in full. Unless, I suppose, you want to clip out chunks of the view’s data. Can’t come up with a practical case in which that would help, but who knows? Just remember that you’re modifying the whole view before it’s rendered, so be careful not to have any runaway regexes or imprecise matching.

You can also feel pretty free to add Blade extensions to your project. Markup extensions require almost no overhead, since Laravel caches rendered pages. Functional extensions are parsed into PHP first, so they’re remarkably quick to render as well. Blade extensions are a great way to save time for small snippets you use often and are more compact than rendering a partial view. I suppose you shouldn’t go too crazy with them, or you might end up putting them together in ways that could better be served by a partial or view composer. Blade extensions should be sprinkled. If you’ve ended up putting most of your markup in Blade extensions you might be doing it wrong.

There it is. I hope this helped you figure out Blade extensions. They’re really easy to use and can leverage the convenience and expression of Blade in specialized ways. You could even create a bundle that contains your favorite Blade extensions. All you’d have to do is register them in the bundle’s start.php file and tell Laravel to auto-load it. Easy as Sunday morning.

Good luck, and happy coding.

Comments