Store strongly typed settings in a Laravel app
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;
public bool $site_active;
public static function group(): string
{
return 'general';
}
}
To set up a class's initial values, you can use a SettingMigration
to ensure that settings are set up correctly.
use Spatie\LaravelSettings\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->save();
return redirect()->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;
public static function group(): string
{
return 'date';
}
public static function casts(): array
{
return [
'birth_date' => 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\LaravelSettings\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);
/**
* Will be used to when retrieving a value from the settings class, and
* inserting it into the repository.
*/
public function set($payload);
}
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;
public function __construct(?string $type)
{
$this->type = $type;
}
public function get($payload): DataTransferObject
{
return new $this->type($payload);
}
public function set($payload): array
{
return $payload->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.
What are your thoughts on "Store strongly typed settings in a Laravel app"?