Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Kryptonit3's avatar

User Settings - Best Practice

I have been going over and over in my head for the past few days on the best way to implement user settings in my projects. Do I want to serialize and store in one column on the user table? Or do I want to create a separate table with a key => value approach. I decided on the key => value approach as it seems as though it is a) easier to scale should I want to add more settings later, b) searchable.

I have a rough working code base but I feel as though it is quite primitive and could use improvement.

Your critique would be greatly appreciated.

// settings table migration
<?php

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

class CreateSettingsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('settings', function(Blueprint $table)
        {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
            $table->string('name');
            $table->string('value');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('settings', function(Blueprint $table)
        {
            $table->dropForeign('settings_user_id_foreign');
        });
        Schema::drop('settings');
    }

}
// User model
<?php namespace App\cablework\Models;

use Illuminate\Database\Eloquent\Model;
use App\cablework\_Traits\UserSettingsTrait;

class User extends Model {

    use UserSettingsTrait;

    ...

    public function settings()
    {
        return $this->hasMany('App\cablework\Models\Settings', 'user_id');
    }

}
// Settings Model
<?php namespace App\cablework\Models;

use Illuminate\Database\Eloquent\Model;

class Settings extends Model {

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

    public function user()
    {
        return $this->belongsTo('App\cablework\Models\User', 'id');
    }

}
// UserSettingsTrait
<?php namespace App\cablework\_Traits;

use App\cablework\Models\Settings;
use Cache;

trait UserSettingsTrait {

    // get setting value
    public function getSetting($name)
    {
        $settings = $this->getCache();
        $value = array_get($settings, $name);
        return ($value !== '') ? $value : NULL;
    }

    // create-update setting
    public function setSetting($name, $value)
    {
        $this->storeSetting($name, $value);
        $this->setCache();
    }

    // create-update multiple settings at once
    public function setSettings($data = [])
    {
        foreach($data as $name => $value)
        {
            $this->storeSetting($name, $value);
        }
        $this->setCache();
    }

    private function storeSetting($name, $value)
    {
        $record = Settings::where(['user_id' => $this->id, 'name' => $name])->first();
        if($record)
        {
            $record->value = $value;
            $record->save();
        } else {
            $data = new Settings(['name' => $name, 'value' => $value]);
            $this->settings()->save($data);
        }
    }

    private function getCache()
    {
        if (Cache::has('user_settings_' . $this->id))
        {
            return Cache::get('user_settings_' . $this->id);
        }
        return $this->setCache();
    }

    private function setCache()
    {
        if (Cache::has('user_settings_' . $this->id))
        {
            Cache::forget('user_settings_' . $this->id);
        }
        $settings = $this->settings->lists('value','name');
        Cache::forever('user_settings_' . $this->id, $settings);
        return $this->getCache();
    }

}
// example controller usage
$user = User::find(1);
$user->setSetting('first_name','John');
$user->setSettings(['first_name' => 'John', 'last_name' => 'Smith']);
// example blade usage
<div class="form-group fg-float @if ($errors->has('first_name')) has-error @endif">
    <div class="fg-line">
        {!! Form::text('first_name', $user->getSetting('first_name'), array('class'=>'form-control', 'required' => '', 'autocomplete' => 'off')) !!}
    </div>
    <label class="fg-label">First Name</label>
    @if ($errors->has('first_name'))
        <small class="help-block">{{ $errors->first('first_name') }}</small>
    @endif
</div>

Everything works that I know of. Keep in mind that all data is validated before inserting with functions.

0 likes
22 replies
ArmadilloThief's avatar

Codepen radio had a good podcast about how they do user settings maybe that may be good to listen to https://blog.codepen.io/2015/02/18/036-settings/

I don't see anything wrong with doing it as key => value pairs it may be awkward to get them as a relation $user->settings will return a collection of setting objects I would personally use a single column in the users table that is json encoded

then add this to your users class

protected $casts = [
    'settings' => 'json'
];

now when you go $user->settings it will return you an object

1 like
RokSiEu's avatar

I think that this would be great lesson on Laracasts. We know that most of the apps need to have some kind of user setting, let it be theme chooser or notification toggle buttons. We can hope that @JeffreyWay will hear us ;)

martinbean's avatar

@Kryptonit3 I implemented settings recently using the same approach: a column for user ID, a key, and a value. I then had a settings repository that fetched settings for a user as an associative array (key–value pairs), rather than have them as a relation on my user model.

1 like
Kryptonit3's avatar

@martinbean I will probably do something similar once I figure out how to use/create a repository. I am working on implementing a third column, type that will either be enum or accept a few strings: string,integer,boolean and when retrieved, cast it into the array as such instead of everything being loaded as a string.

Kryptonit3's avatar

I have updated my site settings function to allow for casting settings as their data type. Due to all settings being stored in the database as a string, some people will need use of storing user boolean settings like receive_notification_emails etc.

// UserSettingsTrait
<?php namespace App\cablework\_Traits;

use App\cablework\Models\Settings;
use Cache;

trait UserSettingsTrait {

    // get setting value
    public function getSetting($key)
    {
        $settings = $this->getCache();
        $data = $this->searchSetting($key, $settings);
        $value = $data['value'];
        $type = $data['type'];
        switch($type) {
            case 'string':
                return (string) $value;
            case 'boolean':
                return (bool) $value;
            case 'integer':
                return (int) $value;
        }
    }

    private function searchSetting($key, $array, $column = 'key')
    {
        foreach ($array as $k => $val) {
        if ($val[$column] == $key) {
            return $val;
        }
    }
        return null;
    }

    // create-update setting
    public function setSetting($key, $value, $type = 'string')
    {
        $this->storeSetting($key, $value, $type);
        $this->setCache();
    }

    // create-update multiple settings at once
    public function setSettings($data = [])
    {
        foreach($data as $setting)
        {
            $key = $setting['key'];
            $value = $setting['value'];
            $type = isset($setting['type']) ? $setting['type'] : 'string';
            $this->storeSetting($key, $value, $type);
        }
        $this->setCache();
    }

    private function storeSetting($key, $value, $type)
    {
        $record = Settings::where(['user_id' => $this->id, 'key' => $key])->first();
        if($record)
        {
            $record->value = $value;
            $record->type = $type;
            $record->save();
        } else {
            $data = new Settings(['key' => $key, 'value' => $value, 'type' => $type]);
            $this->settings()->save($data);
        }
    }

    private function getCache()
    {
        if (Cache::has('user_settings_' . $this->id))
        {
            return Cache::get('user_settings_' . $this->id);
        }
        return $this->setCache();
    }

    private function setCache()
    {
        if (Cache::has('user_settings_' . $this->id))
        {
            Cache::forget('user_settings_' . $this->id);
        }
        $settings = Settings::where('user_id',$this->id)->get()->toArray();
        Cache::forever('user_settings_' . $this->id, $settings);
        return $this->getCache();
    }

}
// settings table migration
<?php

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

class CreateSettingsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('settings', function(Blueprint $table)
        {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
            $table->string('key');
            $table->string('value');
            $table->string('type');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('settings', function(Blueprint $table)
        {
            $table->dropForeign('settings_user_id_foreign');
        });
        Schema::drop('settings');
    }

}
// Settings Model
<?php namespace App\cablework\Models;

use Illuminate\Database\Eloquent\Model;

class Settings extends Model {

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

    public function user()
    {
        return $this->belongsTo('App\cablework\Models\User', 'id');
    }

}

Example usage

$array = [
    [
        'key' => 'first_name',
        'value' => 'John'
    ],
    [
        'key' => 'last_name',
        'value' => 'Smith'
    ],
    [
        'key' => 'age',
        'value' => 20,
        'type' => 'integer'
    ],
    [
        'key' => 'receive_notification_emails',
        'value' => 1, // or true
        'type' => 'boolean'
    ]
];
// $user is User object  - no third parameter means default 'string'
$user->setSettings($array);

// set a single setting - no third parameter means default 'string'
$user->setSetting('first_name','John');

$user->setSetting('profile_is_public',1,'boolean'); // or ('profile_is_public',true,'boolean') or 0 / false
if($user->getSetting('receive_notification_emails')) // returns boolean true, if not set, returns null
{
    // Send user notification email
}

Just for those needing extra functionality. I am sure there is more than one way to do settings but this works for me.

2 likes
antoineaugusti's avatar

Your trait is really clogged with these Eloquent calls. You really want to move that to a repository as soon as you can. If you plan to use caching to speed up things, you may want to look at the decorator pattern for your repository. Take a look at these videos: https://laracasts.com/lessons/the-decorator-pattern and https://laracasts.com/lessons/decorating-repositories

I've also written a blog post about the decorator pattern in repositories that may help you: http://blog.antoine-augusti.fr/2015/02/decorator-pattern-repositories/

1 like
JeffreyWay's avatar
Level 59

Okay - I'll do a video to show my take on user settings. There are definitely situations when the JSON blob makes good sense. Just depends on the project.

27 likes
starter-dev's avatar

@ganeshghalame Just to let you know guys. This lesson is no longer available. This is the whole message when you try to view the page:

"Lesson Retired - Apologies! We have officially retired this lesson. Its contents are no longer current or helpful."

Hoping there will be a new lesson about this. Thank you!

3 likes
ickmund's avatar

Sorry if this is a bit OT...

What is the benefit of doing a key / value store for this, instead of having defined columns on the settings table? Unless the keys are user defined, it seems like a poor tradeoff for me, having to write all that code to replicate existing database behaviour.

Kryptonit3's avatar

@ickmund - for sites that constantly add/remove settings. If you have 100,000 users with a settings table that matches that, it would be taxing to remove/add columns to the settings table, and depending on how many settings you have, it could get pretty wide (a lot of columns). This approach is more of a use it if you want to approach. Both approaches are used just depends on what your app needs. I feel this approach scales much better than adding additional columns to a settings table when you decide you need more settings for your site in the future. It also doesn't require you to create migrations to add settings. Just pick a setting name [key], value, and type and you are done.

@antoineaugusti - I have yet to master creating/using a repository. It is on the list. But thank you for the links and input. Depending on the output of @JeffreyWay video, I may end up changing my setup entirely. :)

starter-dev's avatar

@ArmadilloThief Just to let you know guys. This lesson is no longer available. This is the whole message when you try to view the page:

"Lesson Retired - Apologies! We have officially retired this lesson. Its contents are no longer current or helpful."

Hoping there will be a new lesson about this. Thank you!

2 likes
JeffreyWay's avatar

Well, at least, the first video is up. We still have a follow-up to do, where we'll talk about a few more things.

5 likes
Kryptonit3's avatar

Awesome. Any reason why the thumbs up feature is missing from this thread?

2 likes
rajaman's avatar

Just subscribed to watch this vid :)

1 like
Yorki's avatar

Recently I've made package for my project which uses Laravel 9 and it allows you to add settings to any Laravel model. It can cast values to primitive types like bool, int, but also to custom classes. Eg.:

$user->settings->get('is_gamer');
$user->settings->set('games_count', 10);

// or global site scoped
Settings::get('display_annoucement');

// more advanced usage with definition of custom class
$address = $user->settings->get('address');
$address->country = 'Poland';
$address->zip = '11-222';
$address->city = 'Warsaw';
$user->settings->put('address', $address);

// any model that implements trait
$article->settings->get('show_breadcrumbs');
$post->settings->get('allow_replies');

You can find it here: https://github.com/npabisz/laravel-settings

2 likes

Please or to participate in this conversation.