Update 2021-10-22
An issue has been created on GitHub: https://github.com/laravel/framework/issues/39232
And a fix has been merged:
https://github.com/laravel/framework/pull/39319
Blade components are a great tool for structuring your view templates into small reusable pieces. But while working with them, I stumbled upon a gotcha that I think you should know about. First, let’s take a look at this anonymous component:
post-title.blade.php
@props(['post'])
<h1 {{ $attributes }}>{{ $post->title }}</h1>
And here is how it is used in, say, a post list page:
@foreach ($posts as $post)
<x-post-title :post="$post" />
@endforeach
In this example, the posts are Eloquent models that were previously loaded from the database. Now, if you run this code, you will notice nothing strange in particular. Everything works as intended. However, if you dig a little deeper, you will find some unexpected behavior:
Every time the component is instantiated, the post model is being serialized to JSON.
The cause for this behavior lies in the way the Blade component compiler processes attributes inside the sanitizeComponentAttribute
function.
/**
* Sanitize the given component attribute value.
*
* @param mixed $value
* @return mixed
*/
public static function sanitizeComponentAttribute($value)
{
return is_string($value) ||
(is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString'))
? e($value)
: $value;
}
Basically, any object that implements a __toString
function will be converted to a string. In the case of an Eloquent model, this will convert it to JSON. Depending on how large your model is and how often the component is used, this will waste a significant amount of resources on constructing JSON strings that are never used.
Solution: Use class based components
Instead of an anonymous component, use a class based component and add your props to the constructor arguments. After you do that, your objects will no longer be converted to strings.
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class PostTitle extends Component
{
public $post;
public function __construct($post)
{
$this->post = $post;
}
public function render()
{
return view('components.post-title');
}
}