Adding user-specific settings in Laravel
Tags: laravel, php, webdev, opensource
Published: 2022-04-28 19:58:39

The latest feature we added to [OLM](https://openlittermap.com/community) is the ability to make yourself known to the world using your social media accounts. The links to your accounts appear on the photos you upload, in the Global Map. See the end result in this [photo on the map](https://openlittermap.com/global?lat=41.3254775&lon=19.8288426&zoom=17&photo=278127). This is the approach we're following in our back-end.

There are many ways to handle this, but after some googling, we came to the conclusion that the best solution in our case was to use a `settings` column in our `User` model. Database JSON columns come to the rescue. We copied a good chunk of code from [this awesome post](https://itnext.io/adding-user-settings-to-a-laravel-app-7f97315f7ba7) 🤣.

##Storing the settings in the database

Starting off with adding the new column to the `users` table, this simple migration does the trick:

{% gist https://gist.github.com/GeniJaho/1d55d30a614adcef5b74c802c6973a34 %}

We want it to be nullable since existing users of the platform won't have any settings whatsoever, and it's easier than updating everyone with a default value. We could also provide a default value to this JSON column, like `{}`, but it's only supported from [MySQL version 8.0.13](https://stackoverflow.com/a/61169233/5828796) onwards.

To save the user's settings, we have a `SettingsController` and a method we called `update()`:

{% gist https://gist.github.com/GeniJaho/d8f610cc25c880d6dba855dd3fc5654c %}

Notice that we're using the `$request->validated()` method instead of `$request->all()`. That's because we don't want any setting that we don't have validation for to be stored in our database. The `settings()` method on the `User` model is a nice helper that does the actual heavy lifting.

{% gist https://gist.github.com/GeniJaho/3a9acd52b5bc38c6b23c047db717799b %}

It simply merges the new settings we add with the existing ones, overriding the old values where the keys match. Again, inspired by [this awesome post](https://itnext.io/adding-user-settings-to-a-laravel-app-7f97315f7ba7).

Data validation is handled in a [FormRequest](https://laravel.com/docs/9.x/validation#form-request-validation):

{% gist https://gist.github.com/GeniJaho/fad31deea652d5d974ede647de97e662 %}

The `UpdateSettingsRequest` does just some simple validation, it requires that the links provided be valid URLs. Through the `messages()` method we can [override the default error messages](https://laravel.com/docs/9.x/validation#customizing-the-error-messages) to be more user-friendly.

##Sprinkle some getters on the User

Now that we have a simple way to set a user's settings, we need a simple way to get them as well.

{% gist https://gist.github.com/GeniJaho/ceb2839f7abc2f482795003d5182d603 %}

From top to bottom:

1. It feels very comfortable when using the `@property` doc blocks. Through them, we notify our IDE that, on every `$user` instance, we can access properties like `$user->settings` and `$user->social_links`, and they will return an `array`. The more you have of these doc blocks, the merrier.
2. We add the `'settings' => 'array'` pair to the `$casts` array so that we don't have to `json_encode()` and `json_decode()` all the time to access the settings. You can also use `'json'` instead of `'array'`.
3. The `setting()` method is straightforward, it simply checks if we have a setting with a given name and returns it, otherwise, it returns the default value provided. One gotcha here is that if the setting exists, but has a null-ish value, this method will return that instead of the default fallback. We want that by the way, at least for now. Also, notice `$this->settings ?? []`, we need this check since the column could be null.
4. The `getSocialLinksAttribute()` method will allow us to access it like `$user->social_links`, and it will return only the settings that are not null-ish. This is for easier access in the front-end or other places in code. From Laravel 9 and above, there's a better way to [define Accessors and Mutators](https://laravel.com/docs/9.x/eloquent-mutators#defining-an-accessor) on your models, you should check it out, it's the new kid in the block.

##Does this work with nested settings?

In closing, we didn't go with a nested settings approach, something like this:

{% gist https://gist.github.com/GeniJaho/4b32e8c23e2cdaabde46ca9bab1d1f84 %}

The reason is the complexity of dealing with it in the front end. Saving and updating nested settings would require some deep watching of properties and suspicious object merging operations. This solution does allow us to do that as it is implemented here, but we're keeping it simple for now.

See you in the next post, and thank you for making it here.

> The code used for illustration is taken from the [OpenLitterMap](https://github.com/OpenLitterMap/openlittermap-web) project. They're doing a great job at creating the world's most advanced open database on litter, brands & plastic pollution. The project is open-sourced and would love your contributions, both as users and developers.

---

Originally published at [https://genijaho.dev](https://genijaho.dev/blog/adding-user-specific-settings-in-laravel).