Optional parameters in Laravel routes are meaningless... - Opinion

Look at something very curious about Laravel, which I hadn't really noticed until now. I'm working with a route that has a completely optional parameter, and I'll explain what I discovered.

I have a route that points to /store/{productType?}, where productType is a model (a category or classification). This can be optional, as I indicate in the definition.

When /store is accessed without any parameters, the value should be null or simply undefined.

When /store/zapatos is accessed, then it is defined.

Now comes the curious part. You'd think that if this parameter is optional and isn't passed, the value should be null, right?

Since in Livewire I receive this value in mount(), I assumed I could simply do something like:

function mount(?ProductType $productType)
{
   $this->productType = $productType->id ? $productType : null;
}

In the case of /store/zapatos, everything works as expected: $productType has a valid value.

But in /store, instead of receiving a null value, Laravel passes me an empty instance of the ProductType model.

This behavior is a bit shocking to me. It's as if Laravel is doing this internally:

$productType = new ProductType;

This makes sense in certain contexts, such as when working with forms to create or edit models, where you want an empty instance for code reuse. But in this specific case, it doesn't make sense.

I assume this has to do with null safety, something that was more formally implemented in recent versions of PHP. But it bothers me because:

It can break your logic if you assume you're working with a valid database object.

You can access the title ($productType->title) and it returns null, but you don't know if that's because it doesn't exist in the database or because Laravel passed you an empty instance.

What If I Force the Parameter to be Null?

I thought about forcing the parameter type in mount() to be nullable:

function mount(?ProductType $productType = null)
{
   ***
}

But if you access /store, Laravel throws a 404 directly if it doesn't find the model. That is, it doesn't even reach the component. This bothers me even more because it breaks the route for no apparent reason.

How I wish it would work

I would prefer if Laravel would let me work with null directly. It's cleaner for validations and for code maintainers.

For example, it's much easier to validate:

if ($productType) 

Instead:

if (is_null($productType->title)) ...

The latter creates confusion for any subsequent developers:

Is the title null because you defined it that way, or because Laravel created an empty object?

In summary

  • Laravel doesn't return null when the model is optional and cannot be found: it returns an empty instance.
  • If you define the parameter as nullable, Laravel returns a 404 if the model doesn't exist.
  • This behavior is odd and a bit annoying, at least to me.
  • I don't know the exact version that's been working this way, but I suspect it's a recent one.

I agree to receive announcements of interest about this Blog.

We make an example of an optional parameter in the route, defining the parameter as null and testing in general.

- Andrés Cruz

En español