Dynamic settings in laravel via config function

babak271

Babak

Posted on February 1, 2022

Dynamic settings in laravel via config function

Who is this for?

If you have some parameters in your project that may change in the future, you should make them dynamic to change them easily, because your client may decide to update those parameters or you may feel some restriction parameter should be updated in the future.
In this article, I'm gonna show a straightforward method that enables you to dynamically change settings from your database and cache them to not query the database in every run.

Why do we need dynamic settings?

At first, it seems trivial to update a few parameters during the lifetime of a project, you may be requested to just change this value and commit it. Okay, but after a while, the below questions may come to you.

  • Why do they call me (a programmer) to change a setting?
  • Why do I need to commit a hard-coded parameter?
  • I've delivered this project, why do they ask me to work on it?

The problem is these hard-coded parameters must be dynamic to be changed easily, but how?
Is this a good idea to create a new file in the config folder and save these parameters in it to easily change them later? Or binding them to the .env file to not have to do a commit for every change?

Okay, it seems good to have a config file and bind its value to .env, laravel has a basis for this and we can use laravel's helper functions such as config() for retrieving these values. But I don't agree with this solution. You have to deliver this project, and after that, you should not be called for every change in the project. Or, your client may have an operator that doesn't know about the .env file and its confidential parameters, and many other reasons for not storing a dynamic parameter in .env.

So, how to alleviate it?

Storing parameters in Database

We should store a dynamic value in a dynamic area and a database is made for this job. So, we need a table for storing these parameters, this table may have a simple structure like below, we can create a Setting model and migration by running php artisan make:model Setting -m.

setting migration file

class CreateSettingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('settings', function (Blueprint $table) {
            $table->id();
            $table->string('key')->index();
            $table->text('value');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('settings');
    }
}
Enter fullscreen mode Exit fullscreen mode

And for model we have

class Setting extends Model
{
    protected $fillable = [
        'key',
        'value',
    ];

    public $timestamps = false;
}
Enter fullscreen mode Exit fullscreen mode

Up to this point, we don't have any complex structure for storing or retrieving our setting parameters. We can use \App\Models\Setting::updateOrCreate() for updating or creating a setting and \App\Models\Setting::where('key', 'someKey')->first() for retrieving the setting.

Using laravel's config

It would be really great if I could get settings using laravel's config infrastructure, by doing this, one can easily get a value using config('param_name') function.

We can have a settings file in laravel's config folder named settings.php and define every parameter in it with a default initial value (we want to make them dynamic). This file can be something like below

config/settings.php

<?php

return [
    'main' => [
        'param1' => 'default_value_1',
        'param2' => 'default_value_2',
    ],
];
Enter fullscreen mode Exit fullscreen mode

After that, we need to tell laravel to read this file and include its values to laravel's global config object. So, let's define a function in Setting model and call it in AppServiceProvider (Or a dedicated provider).

Setting model

class Setting extends Model
{
    ...

    public static function chargeConfig()
    {
        $settings = collect(require config_path('settings.php'))
            ->each(fn($value, $key) => app('config')->set(['setting.' . $key => $value]));

        return $settings;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have defined all values of the config file to Laravel's global config object. So we can reach them via config() function.

Let's call chargeConfig() method from ServiceProvider

AppServiceProvider

namespace App\Providers;

use App\Models\Setting;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Setting::chargeConfig();
    }
}
Enter fullscreen mode Exit fullscreen mode

Store setting in database and cache them

These settings should be inserted into the database as we discussed earlier. But, there is a problem in front of us, how we can store these nested arrays into DB?

Fortunately, Arr helper class has a method named dot() which converts a nested array to a dotted one, so we can store an array with its structure into database and change their values in database with ease.

First, we add a new method to the Setting model for caching results of the settings table and we update chargeConfig() method to read from the cache.

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Collection;

class Setting extends Model
{
    ...

    public static function chargeConfig()
    {
        $settings = Cache::get('db_setting', []);

        if ($settings instanceof Collection) {
            collect($settings)
                ->each(fn ($setting) => app('config')->set(['settings.' . $setting->key => $setting->value]));
        }

        return $settings;
    }

    public static function refreshCache()
    {
        Cache::forget('db_setting');
        Cache::rememberForever('db_setting', fn() => static::query()->get()->toBase());

        return self::chargeConfig();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's define a command for storing/updating settings into database using php artisan make:command LoadSettingsFromFile.

namespace App\Console\Commands;

use \Illuminate\Console\Command;
use \App\Models\Setting;
use \Illuminate\Support\Arr;

class LoadSettingsFromFile extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'settings:load-file';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Load settings from file, this command is not overriding db values.';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $settings = \Arr::dot(require config_path('settings.php'));
        foreach ($settings as $key => $value) {
            Setting::query()
                ->firstOrCreate([
                    'key' => $key,
                ], [
                    'value' => $value,
                ]);
        }

        Setting::refreshCache();

        $this->info('Setting file has been loaded successfully.');

        return Command::SUCCESS;
    }
}
Enter fullscreen mode Exit fullscreen mode

Execute this command by running php artisan settings:load-file and then you have your settings in the database.

Last word

Now, you can update settings in database and after that run settings:load-file command to update the cached values in your application.
This command can be executed after each update in database or on a scheduler or called via your admin panel using \Artisan::call('settings:load-file').

I love to hear from you 😍, so feel free to share your thoughts and critics with me in the comments. 👀

💖 💪 🙅 🚩
babak271
Babak

Posted on February 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related