Stefanos Kargas
Stefanos Kargas

Reputation: 11053

Store config in database in Laravel

I am currently using Laravel 5.2. I want to be able to store config properties (key-value pairs) in database, that I am willing to access from both my application on runtime and the console (either php artisan command or Tinker).

What is my best option?

The reason I am interested in database config, is because we often replace/delete files during deployment. Also it would be nice to store values encrypted. Also important feature here is to be able to easily get values via either php artisan or tinker

Upvotes: 2

Views: 3672

Answers (3)

Fikri Mastor
Fikri Mastor

Reputation: 11

I'm using laravel 9, and using package from spatie: spatie/laravel-settings.

If you follow the docs you may set the setting class, for example I want to store payment gateway settings into the database, namely Securepay in Malaysia.

In settings folder, App\Settings will have a new file PaymentGatewaySettings.php:

<?php

namespace App\Settings;

use Spatie\LaravelSettings\Settings;

class PaymentGatewaySettings extends Settings
{
    public string $env;

    public string $uid;

    public string $auth_token;

    public string $checksum_token;

    public static function group() : string
    {
        return 'payment_gateway';
    }
}

In AppSeviceProvider.php we add new line under boot method:

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        /**
         * Payment Gateway settings
         *
         */
        if(DB::table('settings')->where('group', 'payment_gateway')->exists()) {
            config()->set('services', array_merge(config('services'), [
                'securepay' => [
                    'env' => app(SecurepaySettings::class)->env,
                    'uid' => app(SecurepaySettings::class)->uid,
                    'auth_token' => app(SecurepaySettings::class)->auth_token,
                    'checksum_token' => app(SecurepaySettings::class)->checksum_token,
                ]
            ]));
        }
    }

If we do not put the if statement, it would be an error while want to run php artisan command.

In other cases you may extend the Illuminate\Foundation\Application class, and you may use something like this app()->getSecurePayEnv() in everywhere in you application, but to set the config I'm still using boot method in AppSeviceProvider.php.

Hope it helps.

Upvotes: 1

pawcioo o
pawcioo o

Reputation: 9

I extended Rob Biermann approach to handling json data

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;

class Setting extends Model
{
    use HasFactory;

    protected $casts = [
        'value' => 'array'
    ];

    protected $fillable = [
        'key',
        'value'
    ];

    /**
     * @var Setting[]|\Illuminate\Database\Eloquent\Collection|null
     */
    static public $settings = null;

    static function getAll(string $key, $default = null){
        if (empty(self::$settings)) {
            self::$settings = self::all();
        }
        $keys = explode('.', $key);
        $databaseKey = $keys[0];
        unset($keys[0]);
        $model = self
            ::$settings
            ->where('key', $databaseKey)
            ->first();
        if (empty($model)) {
            if (empty($default)) {
                //Throw an exception, you cannot resume without the setting.
                throw new \Exception('Cannot find setting: ' . $key);
            } else {
                return $default;
            }
        } else {
            return $model->value;
        }
    }

    static function get(string $key, $default = null)
    {
        if (empty(self::$settings)) {
            self::$settings = self::all();
        }
        $keys = explode('.', $key);
        $databaseKey = $keys[0];
        unset($keys[0]);
        $model = self
            ::$settings
            ->where('key', $databaseKey)
            ->first();
        if (empty($model)) {
            if (empty($default)) {
                //Throw an exception, you cannot resume without the setting.
                throw new \Exception('Cannot find setting: ' . $key);
            } else {
                return $default;
            }
        } else {
            if(!empty( $keys)){
                return Arr::get($model->value, implode('.',$keys));
            }
            if(is_string( $model->value)){
                return $model->value;
            }
            if(Arr::has($model->value, 'default')){
                return $model->value['default'];
            }

            return $model->value;

        }
    }

    static function set(string $key, $value)
    {
        if (empty(self::$settings)) {
            self::$settings = self::all();
        }

        $keys = explode('.', $key);
        $databaseKey = $keys[0];
        unset($keys[0]);

        $model = self
            ::$settings
            ->where('key', $databaseKey)
            ->first();

        if (empty($model)) {
            if(!empty($keys)){
                $array = [];
                $model = self::create([
                    'key' => $key,
                    'value' => Arr::set($array, implode('.',$keys), $value)
                ]);
            }
            else{
                $model = self::create([
                    'key' => $key,
                    'value' => $value
                ]);
            }

            self::$settings->push($model);
        } else {
            if(!empty($keys)){
                $old = $model->value;
                if(is_string($old)){
                    $old = ["default" => $old] ;
                }
                if(Arr::has($old, implode('.',$keys))){
                    $old = Arr::set($old, implode('.',$keys), $value);
                }
                else{
                    $old = Arr::add($old, implode('.',$keys), $value);
                }

                $model->update(['value' => $old]);
            }
            else{
                if(is_array($model->value)){
                    $new = $model->value;
                    $new['default'] = $value;
                    $value = $new;
                }
                $model->update(['value' => $value]);

            }

        }
        return true;
    }


}

now u can use

Setting::get('someKey.key');
Setting::get('someKey.key.key1');
Setting::set('someKey.key', 'test');
Setting::set('someKey.key.key1', 'test');

Upvotes: 0

Techno
Techno

Reputation: 1696

  1. Make a migration: php artisan make:migration CreateSettingsTable
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

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

            $table->unique([
                'key', //I add a unique to prevent double keys
            ]);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('settings');
    }
}
  1. Make the model: php artisan make:model Setting
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

    //I would normally do this in a repository,
    // but for simplicity sake, i've put it in here :)
    static public $settings = null;

    static function get($key, $default = null)
    {
        if (empty(self::$settings)) {
            self::$settings = self::all();
        }
        $model = self
            ::$settings
            ->where('key', $key)
            ->first();
        if (empty($model)) {
            if (empty($default)) {
                //Throw an exception, you cannot resume without the setting.
                throw new \Exception('Cannot find setting: '.$key);
            }
            else {
                return $default;
            }
        }
        else {
            return $model->value;
        }
    }

    static function set(string $key, $value)
    {
        if (empty(self::$settings)) {
            self::$settings = self::all();
        }
        if (is_string($value) || is_int($value)) {
            $model = self
                ::$settings
                ->where('key', $key)
                ->first();

            if (empty($model)) {
                $model = self::create([
                    'key' => $key,
                    'value' => $value
                ]);
                self::$settings->push($model);
            }
            else {
                $model->update(compact('value'));
            }
            return true;
        }
        else {
            return false;
        }
    }
}

Please note here, that I added the get and set functions, together with a static $settings variable directly to the model, to keep the example small. Usually I would opt to making a repository or service(not serviceprovider) to handle these functions. This way you only query db once(per request) for all the settings. You could stick this in cache, but that is not part of this answer of now.

  1. Run php artisan migrate to ge the table in the db.

  2. Run composer dump-autoload to make sure tinker can find the Setting class.

  3. Use someting like php artisan tinker(https://laravel.com/docs/7.x/artisan#tinker) to test it, in this case you can do:

Setting::set('someKey', 'someValue'); //Set someKey to someValue
Setting::get('someKey'); //Get someKey, throws exception if not found
Setting::get('somekey2', 'someDefault'); //Shows someDefault because somekey2 is not set yet.

I hope it helps! :)

Upvotes: 1

Related Questions