loading...

Adding user settings to a Laravel app

mattkingshott profile image Matt Kingshott 👨🏻‍💻 Originally published at itnext.io on ・4 min read

This article is part of a series where I document insights, changes and rethinking that I experienced while refactoring the codebase for Pulse — a painless and affordable site & server monitoring tool designed for developers.

Today, I’d like to talk about adding user-level settings. There are several ways you can go about doing this, so we’ll consider them before moving onto the choice I made for Pulse, as well as the code that was required to make it work.

So many possibilities

As your app grows in size, it becomes more and more likely that your users will want or even need to customise their experience when using it. As a result, providing the functionality for them to tweak what they see / how the app works becomes increasingly vital to its continued success.

The simplest way to address this is with user-level settings, however the implementation you use can vary significantly. In the case of Pulse, I opted for the database approach as I had no criteria that favoured a file-based format. That being said, I still had a decision to make… do I simply add a settings column to the Users table, or would I be better off with a Settings table and attaching them to a User via a foreign key?

While the later option does give you more power, it requires a second query to be performed in order to pull the user’s settings. By contrast, since Laravel already pulls in the user record for the authenticated user, adding an extra column saves me from performing a second query.

Adding the functionality to users

The first thing we need to do, is to add the settings column to the users table:

// Users migration
$table -> text("settings");

You could also opt for a larger text format if you needed, but in most cases, the standard text format should provide sufficient space for all of your data.

Next, we need to tell Laravel that this attribute is an array and should be JSON encoded / decoded when accessing / setting its contents. We can easily do this by adding the column to the $casts array on the User model, like so:

// User model
protected $casts = ["settings" => "array"];

Retrieving a setting for a user

Now it’s time to add some helper methods to work with our settings. We’ll begin with a simple accessor that allows us to specify the name of a setting that we want, as well as a fallback value that we can use if the setting does not exist (we’ll use null by default):

/\*\*
 \* Retrieve a setting with a given name or fall back to the default.
 \*
 \*/
public function setting(string $name, $default = null)
{
    if (array_key_exists($name, $this->settings)) {
        return $this->settings[$name];
    }

    return $default;
}

If you want to retrieve all of the user’s settings at once, you can simply access the settings property on the User model itself, like so:

$settings = $user->settings;

Writing settings to the database

Beyond the accessor, we’ll also need a means of updating our settings. However, it’s not as simple as overwriting array. If we did that, we’d remove any settings that aren’t in the new array that we’re using as the replacement.

In addition, it’s reasonable to assume that the app has a settings page where you can adjust and save multiple settings at once, so to mitigate both of this issues, we’ll use array_merge to ensure that any new settings are automatically appended, while any existing settings are updated to use their new values (assuming a new value is provided).

We’ll also add a boolean $save parameter, which is set to true by default. That way, calling the method will automatically save the changes, however if you want to perform further operations on the User model prior to saving, you have the option to disable the save functionality. We’ll call this method “settings” since we are potentially working with more than one setting:

/\*\*
 \* Update one or more settings and then optionally save the model.
 \*
 \*/
public function settings(array $revisions, bool $save = true) : self
{
    $this->settings = array_merge($this->settings, $revisions);

    if ($save) {
        $this->save();
    }

    return $this;
}

Testing that everything works

Let’s finish off by adding a few unit tests that confirm the settings methods work as expected. We’ll test that we can access a setting, that we can fall back to a default value, and that we can save revised settings to the database:

/\*\* @test \*/
public function a_user_can_get_a_setting()
{
    $settings = ["settings" => ["foo" => "bar"]];

    $user = factory(User::class, 1)->create($settings);

    $this->assertEquals("bar", $user->setting("foo"));
    $this->assertNull($user->setting("baz"));
    $this->assertEquals(5, $user->setting("baz", 5));
}

/\*\* @test \*/
public function a_user_can_change_settings()
{
    $settings = ["settings" => ["foo" => "bar"]];

    $user = factory(User::class, 1)->create($settings);

    $this->assertEquals(
        "world", 
        $user->settings(["foo" => "world"], false)->setting("foo")
    );

    $this->assertEquals(
        "hello", 
        $user->settings(["baz" => "hello"], false)->setting("baz")
    );

    $this->assertEquals(
        ["foo" => "bar"], $user->refresh()->settings
    );
}

/\*\* @test \*/
public function a_user_can_change_and_save_settings()
{
    $settings = ["settings" => ["foo" => "bar"]];

    $user = factory(User::class, 1)->create($settings);

    $this->assertEquals(
        "world", 
        $user->settings(["foo" => "world"])->setting("foo")
    );

    $this->assertEquals(
        ["foo" => "world"], $user->refresh()->settings
    );
}

Wrapping up

While Laravel doesn’t include user-level settings out of the box, as you’ve seen in this article it’s trivial to add such functionality, as well as to cater for defaults, auto-saving and mass updating.

I have more articles to share, so if you’re interested in reading them, be sure to follow me here on Medium. You can also follow me on Twitter.

Lastly, if you’re in the market for an affordable and painless site & server monitoring tool that doesn’t require you to have a DevOps degree, please take a moment to check out Pulse. I think you’ll find it to a be a breath of fresh air!

Thanks again and happy coding!


Posted on Sep 9 '19 by:

mattkingshott profile

Matt Kingshott 👨🏻‍💻

@mattkingshott

Founder. Developer. Writer. Lunatic. Created Pulse, IodineJS, Axiom, and more. #PHP #Laravel #Vue #TailwindCSS

Discussion

markdown guide
 

@mattkingshott pulse looks interesting. I have just 1 site I need to monitor, what would the pricing be / mo? It's unclear from the landing page what a unit means.

 

Hi there! Sorry you found it unclear. A unit is simply a site or a server. So if you want to monitor 1 site, it’ll cost you $1 per month. Hope that clears things up.