Parsing Blade Directives
Learn how to add more components to the blade directive validator.
We'll cover the following...
Implementation of the BladeDirectiveValidator
class
Our next task will be to set things up to extract information about any Blade directives that appear within our input text. In the previous chapter, we explored the idea of using a regular expression to help us build an index of where we should start looking for parseable items within a document. We will do the same for the Blade directives because this will help us avoid escaped Blade directives as well as things that look like directives that should be ignored, such as CSS media queries.
To do this, we will need a list of the core directive names and a way to provide a list of custom directives, which can be populated at runtime by retrieving them from the Blade compiler within the context of a running application. Once we have those items, we can construct a dynamic regular expression that is similar to the following:
/(?<!@)@(?!@)(can|cannot|canany)/i
The regular expression above begins with an obscure pattern. Still, it matches the @
character as long it is not followed or preceded by another instance of the @
character. We must account for both cases to avoid accidentally matching the first @
in an escaped Blade directive.
<?php// ...class BladeDirectiveValidator{protected $structures = [];// ...protected $coreDirectives = ['can', 'cannot', 'canany', 'elsecan', 'elsecannot','elsecanany', 'endcan', 'endcannot', 'endcanany','class', 'component', 'endComponent', 'slot', 'endSlot','props', 'endComponentClass', 'componentClass','componentFirst', 'endComponentFirst', 'push', 'endPush','aware', 'auth', 'elseAuth', 'endAuth', 'env', 'endEnv','production', 'endProduction', 'guest', 'elseGuest','endGuest', 'hasSection', 'if', 'unless', 'elseIf','else', 'endIf', 'endUnless', 'isset', 'endIsset','switch', 'case', 'default', 'endSwitch', 'once','endOnce', 'selected', 'checked', 'disabled', 'required','readonly', 'pushIf', 'endPushIf', 'error', 'enderror','csrf', 'dd', 'dump', 'method', 'vite', 'viteReactRefresh','each', 'include', 'includeIf', 'includeWhen','includeUnless', 'includeFirst', 'inject', 'js', 'json','extends', 'extendsFirst', 'section', 'parent', 'yield','show', 'append', 'overwrite', 'stop', 'endsection','foreelse', 'empty', 'endforelse', 'endempty', 'for','foreach', 'break', 'continue', 'endfor', 'endforeach','while', 'endwhile', 'php', 'unset', 'stack', 'push','pushOnce', 'endpush', 'endpushonce', 'prepend','prependOnce', 'endprepend', 'endprependOnce', 'lang','endlang', 'choice'];protected $customDirectives = [];public function setCustomDirectives(array $directives){$this->customDirectives = $directives;}public function getDirectiveNames(){return array_merge($this->coreDirectives,$this->customDirectives);}public function parse($value){$value = $this->prepareInput($value);$htmlRegions = collect(token_get_all($value))->filter(function ($token) {return is_array($token) && $token[0] == T_INLINE_HTML;})->all();$pattern = collect($this->getDirectiveNames())->map(function ($name) {return preg_quote($name);})->implode('|');foreach ($htmlRegions as $region) {preg_match_all('/(?<!@)@(?!@)('.$pattern.')/i',$region[1], $matches,PREG_OFFSET_CAPTURE);$directiveIndex = $matches[0];}}}
We have added a significant amount of code in the above, with the vast majority of it related to defining the list of core Blade directives and setting custom directives. Our additions between lines 57 and 75 are more interesting, however.
Between lines 57 and 60, we use Laravel’s ...