PHPnews.io

★ Store strongly typed settings in a Laravel app

Written by murze.be / Original link on Nov. 9, 2020

We have released a new package, called spatie/laravel-settings, that allows you to strongly type settings in a Laravel app. In this blog post, I'd like to introduce the package to you.

Why we created this package #

At Spatie, we're currently building a huge application. This application will be multi-tenant, and each tenant has literally hundreds of settings that can be configured to tweak behavior.

The app's admins can update the settings, so it's only logical to store these settings in the database. We could have created a table called settings with a name and a value column and call it a day. But we want to store different types of settings: strings, integers, booleans, and objects such as dates. And it would also be nice if we could easily fetch these settings from anywhere in our app and get them in the right type.

Because we think our solution might help others, we decided to package it up.

Introducing spatie/laravel-settings #

The package is built around settings classes. They are responsible for storing the settings. They all have public properties that can be used to fetch settings values. Each settings class extends from Spatie\LaravelSettings\Settings. They have a static method group that should return a string uniquely grouping settings.

use Spatie\LaravelSettings\Settings;

class GeneralSettings extends Settings { public string $site_name;

<span class="hljs-keyword">public</span> bool $site_active;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">group</span><span class="hljs-params">()</span>: <span class="hljs-title">string</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">'general'</span>;
}

}

To set up a class's initial values, you can use a SettingMigration to ensure that settings are set up correctly.

use Spatie<span class="hljs-title">LaravelSettings<span class="hljs-title">SettingsMigration;

class CreateGeneralSettings extends SettingsMigration { public function up(): void { $this->migrator->add('general.site_name', 'Spatie'); $this->migrator->add('general.site_active', true); } }

To retrieve settings, you can resolve your settings class from the container. Here's an example where we use method injection.

class IndexController
{
public function __invoke(GeneralSettings $settings){
return view('index', [
'site_name' => $settings->site_name,
]);
}
}

Updating the settings can be done by setting the public properties and calling save on the settings class.

class SettingsController
{
public function __invoke(GeneralSettings $settings, GeneralSettingsRequest $request){
$settings->site_name = $request->input('site_name');
$settings->site_active = $request->boolean('site_active');
    $settings-&gt;save();
    
    <span class="hljs-keyword">return</span> redirect()-&gt;back();
}

}

Of course, you could also just instanciate the class using the container:

function getName(): string{
return app(GeneralSettings::class)->site_name
}

Using casts #

Let's take a look at how objects can be used as settings. Here's an example where we are going to use a date object in a setting. Notice that there is now a casts function that returns the casters for a setting's property.

class DateSettings extends Settings
{
public DateTime $birth_date;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">group</span><span class="hljs-params">()</span>: <span class="hljs-title">string</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">'date'</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">casts</span><span class="hljs-params">()</span>: <span class="hljs-title">array</span>
</span>{
    <span class="hljs-keyword">return</span> [
        <span class="hljs-string">'birth_date'</span> =&gt; DateTimeInterfaceCast::class
    ];
}

}

The DateTimeInterfaceCast ships with the package and can be used for properties with types like DateTime, DateTimeImmutable, Carbon and CarbonImmutable.

Writing your own casters is pretty easy. You just need to create a class that implement that Spatie\LaravelSettings\SettingsCasts\SettingsCast interface.

namespace Spatie<span class="hljs-title">LaravelSettings<span class="hljs-title">SettingsCasts;

interface SettingsCast { /** * Will be used to when retrieving a value from the repository, and * inserting it into the settings class. */ public function get($payload);

<span class="hljs-comment">/**
 * Will be used to when retrieving a value from the settings class, and
 * inserting it into the repository.
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span><span class="hljs-params">($payload)</span></span>;

}

Here is an implementation of a caster that can be used with DTOs from the spatie/data-transfer-object package.

class DtoCast implements SettingsCast
{
private string $type;
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span><span class="hljs-params">(?string $type)</span>
</span>{
    <span class="hljs-keyword">$this</span>-&gt;type = $type;
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span><span class="hljs-params">($payload)</span>: <span class="hljs-title">DataTransferObject</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">$this</span>-&gt;type($payload);
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span><span class="hljs-params">($payload)</span>: <span class="hljs-title">array</span>
</span>{
    <span class="hljs-keyword">return</span> $payload-&gt;toArray();
}

}

In closing #

Using this package, we can safely use all of the configuration settings throughout our entire codebase. It's pretty nice that we also get autocompletion on the properties when a settings class is injected.

The package has many other features not mentioned in this blogpost: using Redis, encrypt properties,global casts, locking properties, ... and much more. To learn more about these features, head over to the readme of spatie/laravel-settings on GitHub.

This package has been coded up by my colleague Ruben, who, as always, did an excellent job.

Be sure also to take a look at our other packages and paid products as well. Pretty sure we got something for your next project.

murze

« Marbles2 - 8-Nov 2020 [devlog/marbles2] - ★ Encrypting and signing data using private/public keys in PHP »