So at Cyber-Duck we like to use the excellent Blade Extensions which add various helpful features to Blade.
Unfortunately at present there’s a bug in the package. Typically it manifests itself as error message
Trying to get property of non-object
When trying to access the built-in
$loop variable - a variable so useful, so goddamn glamarous, that they put it
on the front page of their extension! This is the variable that
$i wants to be when it grows up. Programmers
lust over it. And suddenly it’s protesting it’s not an object? What’s going on?
We’ll explore the problem by provoking the bug and then see what we can do solve it. If you don’t care why it works, skip to the end and follow the instruction. Meanwhile, I’ll try to flesh out a full blog article from it. See you in a moment!
In this somewhat contrived example, let’s suppose we want to list some of the latest sites
we’ve discovered recently on our web adventures. Copying and pasting
<li>s in a template is for peasants. Let’s use a loop:
So let’s stick that in the blade template and see what it ends up compiling to.
We can make discovering this considerably easier by appending
@breakpoint to the
above - one of the many useful features that Blade Extensions support. When running with Xdebug and PhpStorm correctly
configured (and why are you developing any other way?), this will handily stop execution at that point in the
compiled file (by inserting a call to
Well, that looks alright doesn’t it? We probably don’t know what the last bit’s about but hey…
The documentation clearly suggests we should be seeing a
$loop variable being introduced somewhere in the plain php.
After all, that’s pretty much what this article’s about. Otherwise it doesn’t matter much. This is so that in principle
we could do something like:
But where’s the
$loop in the above?
A clue comes if we mix things up a bit and define the array before the
That turns out to compile to (give or take a bit of indentation for clarity):
That’s rather different, isn’t it? There’s actually a
$loop variable there now.
So why is this suddenly working and what can we do so that it works without this workaround?
Let’s start at the beginning.
Possibly the simplest way to extend Blade is to register a custom compiler by calling
Blade::extend. These take any non php
strings and return a parsed version - possibly including php.
As an aside, wondering how they avoid dealing with the php without performing black magic? Blade first uses the php tokenizer to
split off the php ‘bits’ - see documentation for token_get_all
then admire the source of
Illuminate\View\Compilers\BladeCompiler to understand how it does this
Anyhow, the approach taken by Blade Extensions to compiling is extremely simple - none of the stuff you learned about when you last studied compilers a decade or so ago - it’s simply a preg_replace.
And the pattern it’s looking for by default is
Great thing about regular expressions is how even simple ones are immediately clear, right? No, me neither. But break that regex down into its constituent parts and we can see it’s actually pretty simple:
/(?<!\\w)Negative look behind - no word character here. In other words, start the regex at a sensible place!
(\s*)Any amount of white space (makes sense)
(?:\s*)Any amount of white space ahead
\((.*)Open bracket, then any amount of any characters…
(?:\sas)At least one white space character, then ‘as’
(.*)\)/Any amount of any character and then close bracket
See thing is, when I said any character above, I was lying. I’m playing games with you. Like a killer whale tossing a seal.
. symbol in PCRE, without the s modifier, matches any character. Apart from new line.
And that’s why it’s not matching our lovely foreach above. So we need to modify it. We could stick in the s modifier, but actually this isn’t a great idea - I have a lovely proof of this, but it’s too big to fit in the margins of this page.
So we have to take an alternative approach. Fortunately, we can keep this simple - we just change the
Well, not quite. Obviously we need to stick it in brackets. So we just change the
Oh wait, everything broke.
Take a look at the full foreach directive (in
In other words, there’s back-references in the replacement string. Which is obvious really, if you actually think about it.
So actually we need to change the
(?:.|\n) to make it into a non capturing group - it’s either that or
change the replacement string to change the backreference indexes, and somehow that feels less elegant to me.
So our final regex now becomes
Alright, we have a regex. How do we use it? A quick peek at the source code makes it clear that over-rides config’s is loaded from blade_extensions.overrides.
So tl;dr - the solution to the problem is to create a file in your project called
config/blade_extensions.php with the following content:
And now you have access to
$loop. Use it wisely.