Dynamic settings in laravel via config function
Tags: laravel, cache, config
Published: 2022-02-01 13:03:48

## 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');
    }
}
```

**And for model we have**

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

    public $timestamps = false;
}
```

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',
    ],
];
``` 
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;
    }
}
```
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();
    }
}
```

## 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();
    }
}
``` 

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;
    }
}
```

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 :heart_eyes:, so feel free to share your thoughts and critics with me in the comments. :eyes: