Caching Laravel configs that use objects
Tags: php, laravel, caching
Published: 2023-04-29 19:51:44

---
title: Caching Laravel configs that use objects
published: true
description: What to do when that config with a `new class` goes boom
tags: php,laravel,caching
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/us02oumuedok5194tvxj.png
canonical_url: https://elliotderhay.com/blog/caching-laravel-configs-that-use-objects
---

**Real quick:** If you’re using `spatie/laravel-markdown` and having this issue (how I ran into this), it was partially fixed with [PR#53](https://github.com/spatie/laravel-markdown/pull/53 "‌") recently. :tada:

---

This headline might be a bit of a head-scratcher for you. It comes from an issue that I ran into recently—and that I didn’t dig into right away.

Basically, running `artisan config:cache` started giving me errors about not being able to serialize my configs.


## Rewind :rewind:

If you haven’t used Laravel before, one of its directories in the project root is `config/`. This directory houses PHP files for project settings—mostly Laravel’s, but you can create and register your own too. Some installed packages also let you publish _their_ configs to that directory for you to customize.

The combined application config is normally rebuilt for every request. To speed this up, Laravel has a `config:cache` command to generate 1 config file. This includes any values that might’ve been resolved dynamically, like from function calls. And it usually “just works”.

## When “just works”, doesn’t :boom:

Sometimes you might need an object in your config, like if you’re using Spatie’s [Laravel Markdown](https://spatie.be/docs/laravel-markdown/v1 "‌") package and creating custom renderers.

For example:

```php
<‌?php
return [
  // ...
  'block_renderers' => [
    ['class' => BlockQuote::class, 'renderer' => new BlockQuoteRenderer(), 'priority' => 1],
    ['class' => Heading::class, 'renderer' => new HeadingRenderer(), 'priority' => 1],
    ['class' => ListItem::class, 'renderer' => new ListItemRenderer(), 'priority' => 1],
  ],
  //...
];
```

Now, this doesn’t look bad. But if you do this as-is and then run `config:cache`, you'll get an error similar to this:

```
  LogicException

  Your configuration files are not serializable.

  at /path/to/project/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php:80
     76▕             require $configPath;
     77▕         } catch (Throwable $e) {
     78▕             $this->files->delete($configPath);
     79▕
  ➜  80▕             throw new LogicException('Your configuration files are not serializable.', 0, $e);
     81▕         }
     82▕
     83▕         $this->info('Configuration cached successfully!');
     84▕     }

  1   /path/to/project/bootstrap/cache/config.php:<line-number>
      Error::("Call to undefined method App\Markdown\Renderers\BlockQuoteRenderer::__set_state()")

  2   /path/to/project/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php:76
      require()
```

## What causes this? :raised_eyebrow:

The error message might need clarifying. For 1, it says “not serializeable”. That might lead you to believe `config:cache` is calling `serialize()` under the hood, but it’s not.

It’s also not pointing out _which_ config it’s having trouble with.

But with some digging through `laravel/framework`, we find [this section of code](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Foundation/Console/ConfigCacheCommand.php#L62-L69 "‌"):

```php
<?php
//...
        $configPath = $this->laravel->getCachedConfigPath();

        $this->files->put(
            $configPath, '<?php return '.var_export($config, true).';'.PHP_EOL
        );

        try {
            require $configPath;
        } catch (Throwable $e) {
          // ...
        }
// ...
```

In there, there’s a call to `var_export()`. If we look at the [PHP manual](https://www.php.net/manual/en/function.var-export.php "‌") for that function, it shows examples of different types of output.

But look specifically at **Example #3: Exporting Classes**. It has this sample output:

```
A::__set_state(array(
   'var' => 5,
))
```

You might remember that that static method, `__set_state()`, appears in the stack trace from earlier.

If we read [PHP’s magic methods page](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state "‌"), we’ll see that this static method allows parsing an exported object string back into an object while automatically providing it with its old exported data!

And rereading that snippet from `laravel/framework`, what’s happening makes sense now. When calling `config:cache`:

1. Laravel is reading in the full, generated config and generating a combined PHP file from that. Then,
2. it’s immediately reading in this pre-built PHP file, triggering calls to that magic method.

So it seems this error is just a side effect of how config caching works.


## So how do we get around this? :thinking:

We can implement `__set_state()` for classes we “new up” in our configs. The `return` value should be an instance of that class too.

For my renderers, I wrote a trait:

```php
<?php

trait Resumeable
{
	public static function __set_state(array $state_array): static
	{
		$object = new static();

		foreach ($state_array as $prop => $state) {
			if (!property_exists($object, $prop)) continue;

			$object->{$prop} = $state;
		}

		return $object;
	}
}
```

I tested this on PHP 8.1. Since it’s being called from within the class it affects, it also works on `private` and `protected` properties.

Good thing too, because `var_export()` exports data from `private` and `protected` properties.


## Summary :clipboard:

You may not need this very often. And if you’re using `spatie/laravel-markdown`, [PR#53](https://github.com/spatie/laravel-markdown/pull/53 "‌") did get merged pretty quickly, so you can just use class strings now (Thanks [erikn69](https://github.com/erikn69 "‌") and [freekmurze](https://github.com/freekmurze "‌")!)

But this is 1 way to get around config caching issues you may run into.

**Thanks for reading!** :wave: