Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

A Laravel package to quickly see which HTML is rendered by which Blade view

Original – by Freek Van der Herten – 6 minute read

I'm happy to share that we have released our latest package, spatie/laravel-blade-comments.

This package can add comments to your rendered HTML output. For each Blade view that was used to build up the response, it adds start and ending comments.

Adding debug comments to rendered output

When looking at the HTML of a rendered page, it may not be evident to you which Blade view is responsible for which HTML. This package will add HTML before and after each rendered view, so you immediately know to which Blade view/component to go to change the output.

When you inspect a part of the page using your favorite browser's dev tools, you'll immediately see which Blade view rendered that particular content. Here's a demo where we inspected the breadcrumbs on our company site. It is immediately clear that the breadcrumbs are rendered by the front.pages.docs.partials.breadcrumbs Blade view.

At the top of the HTML document, we'll also add some extra information about the topmost Blade view and the request.

How the package works under the hood

Creating this package was a small journey. The code that made it into the released versions vastly differs from how the package started.

In our first stab at adding comments before and after each Blade view, we looked at overriding Blade directives. This can be done with Blade::directive. We also invaded (= using a private method of) the Blade compiler.

Here's the class that was responsible for overring the extends Blade directive.

namespace Spatie\BladePaths\Renderers;

use Illuminate\Support\Facades\Blade;
use Illuminate\View\Compilers\BladeCompiler;
use Spatie\BladePaths\Concerns\GetsPathFromDirectiveExpression;

class BladeExtendsRenderer implements Renderer
{
    use GetsPathFromDirectiveExpression;

    public function __construct(protected BladeCompiler $compiler)
    {
    }

    public function register(): void
    {
        Blade::directive('extends', function ($expression) {
            $compiledViewContent = invade($this->compiler)->compileExtends($expression);

            return $this->render($compiledViewContent, $this->getPath($expression));
        });
    }

    protected function render(string $compiledViewContent, string $templatePath): string
    {
        $startComment = "<!-- View extends: {$templatePath} -->".PHP_EOL;

        return "{$startComment}{$compiledViewContent}";
    }
}

This approach had a few drawbacks:

  • we had to override each and every Blade directive
  • the solution relied on calling private Laravel methods, so our solution was very brittle
  • we never got to add a comment after a rendered Blade component to work because of all the magic involved for this in Laravel

At Spatie, we have a fun tradition on the last Friday of the month. From morning to noon, we hold a knowledge-sharing session, where everybody in our team can share the cool things they learned or the problems they are stuck at. After the knowledge-sharing session, we always go to eat in a fancy Italian restaurant.

At the last knowledge-sharing session, my colleague Tim, who is the principal author of spatie/laravel-blade-comments, shared the problems mentioned above.

Our other colleagues couldn't find a solution immediately, but Tim discovered something very useful in Laravel: Blade precompilers.

Here's how you can register a Blade precompiler:

Blade::precompiler(function(string $string) {
	// manipulate the string here
	// ...

	return $string;
});

The callable passed to the precompiler will receive the content of a Blade view before it gets compiled. You can manipulate the string and return your changed version. Such a precompiler is at the heart of our package.

In our package, you'll see that we register a dedicated class to manipulate the Blade content.

// returns the `BladeCommentsPrecompiler` class
$precompilerClass = config('blade-comments.precompiler');

Blade::precompiler(fn (string $string) => $precompilerClass::execute($string));

At the heart of our BladeCommentsPrecompiler class, we have a preg_replace that manipulates the uncompiled Blade view.

namespace Spatie\BladeComments;

use Spatie\BladeComments\Commenters\BladeCommenters\BladeCommenter;

class BladeCommentsPrecompiler
{
    public static function execute(string $bladeContent): string
    {
        foreach (self::commenters() as $commenter) {
            $bladeContent = preg_replace(
                $commenter->pattern(),
                $commenter->replacement(),
                $bladeContent,
            );
        }

        return $bladeContent;
    }

    /**
     * @return array<BladeCommenter>
     */
    protected static function commenters(): array
    {
        return collect(config('blade-comments.blade_commenters'))
            ->map(fn (string $class) => app($class))
            ->toArray();
    }
}

The pattern and replace string used in preg_replace are provided by the various BladeCommenter classes. Here is the class that is responsible for handling anonymous Blade components.

namespace Spatie\BladeComments\Commenters\BladeCommenters;

class AnonymousBladeComponentCommenter implements BladeCommenter
{
    public function pattern(): string
    {
        return '/(##BEGIN-COMPONENT-CLASS##@component\(\'Illuminate\\\\View\\\\AnonymousComponent\',\s*\'([^\']+)\'[^\)]*\)[\s\S]*?@endComponentClass##END-COMPONENT-CLASS##)/';
    }

    public function replacement(): string
    {
        return '<!-- Start anonymous component: $2 -->$1<!-- End anonymous component $2 -->';
    }
}

The package contains many more similar Blade commenters.

Extending the package

Like many other Spatie packages, we made sure that spatie/laravel-blade-comments is very extensible. You can read more about our strategies for making Laravel packages customizable in this blog post.

Want to add more comments to the output? No problem! In the blade_commenters key of the blade_commenters config file, you can add your own BladeCommenter. A BladeCommenter is any class that implements the following interface:

namespace Spatie\BladeComments\Commenters\BladeCommenters;

interface BladeCommenter
{
    /*
     * Should return a regex pattern that will be used
     * in preg_replace. 
     */
    public function pattern(): string;

    /*
     * Should return a replacement string that will be
     * used in preg_replace.
     */
    public function replacement(): string;
}

Take a look at the BladeCommenters that ship with the package for an example.

The package also adds valuable information about the request at the top of the HTML page. This is done by the so-called request commenters . The default request commenters are in the request_commenters key of the blade-comments config file.

You can add your own request commenters there. A RequestCommentor is any class that implements the following interface:

namespace Spatie\BladeComments\Commenters\RequestCommenters;

use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

interface RequestCommenter
{
    public function comment(Request $request, Response $response): ?string;
}

If the comment function returns a string, it will be injected at the top of the HTML document. Take a look at the request commenters that ship with the package for an example.

In closing

I want to thank my colleague Tim for the idea behind this package. Tim also coded up the core part of the package, and I helped to polish it. All our other colleagues at Spatie provided feedback as well.

To learn more about the package, head to spatie/laravel-blade-comments on GitHub.

This isn't the first package, we've made. Please look at this extensive list of Laravel and PHP packages we've made before. I'm sure there's something there for your next project. If you want to support our open-source efforts, consider picking up one of our paid products or subscribe at Mailcoach and/or Flare.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Fred Gentry avatar

How does this impact performance on production? Presumably it is using debug_backtrace, a quick https://freek.dev/2235-learn-how-to-write-readable-php-that-is-a-joy-to-maintain basket random came up with differing opinions on whether debug_backtrace had an impact on speed.

Adam Sara avatar

Laravel is designed to be highly flexible, allowing you to customize and extend it in many different ways. So instead of a single package Slither io that covers all use cases, there are many different approaches to achieving your goals.

Ralph McGee avatar

Whether you're aiming for speed, agility, or a mix of both, you’ll find the perfect board to match your style in snow rider.

Bekean Loinse avatar

Drift Hunters is the perfect game to relax with while still getting that adrenaline rush from drifting

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.