Пользовательские настройки в приложении

user-level settings in a laravel apps

Сегодня поговорим о добавлении настроек на уровне пользователя. Есть несколько способов сделать это и мы сначала рассмотрим их, а потом перейдем к выбору, который я сделал в своем приложении Pulse.

Так много возможностей

По мере того как ваше приложение растёт, всё более вероятно, что ваши пользователи захотят настраивать его под себя. И предоставление им возможности делать это — очень важно для дальнейшего успеха приложения.

Самый простой способ решить эту проблему — пользовательские настройки, причем их реализация может значительно различаться. В случае с Pulse я остановился на базе данных. При этом у меня были варианты, либо просто добавить столбец настроек в таблицу Users, либо сделать таблицу с настройками и соединить её с пользователем с помощью внешнего ключа.

Хотя второй вариант дает больше возможностей, но требует выполнения второго запроса, для получения пользовательских настроек. В то время как Laravel уже подгрузила запись для аутентифицированного пользователя и добавление дополнительного столбца избавляет от дополнительного запроса к базе данных.

Добавление функционала

Первое, что нам нужно сделать, это добавить столбец настроек в таблицу пользователей:

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

Вы можете выбрать формат большого текста, если нужно, но в большинстве случаев стандартного текста должно хватить для всех ваших данных.

Далее, нужно сказать Laravel, что этот атрибут является массивом и должен использовать JSON кодирование/декодирование при доступе к нему. Мы можем легко сделать это, добавив столбец в массив $casts в модели User, например:

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

Получение пользовательских настроек

Пришло время добавить несколько вспомогательных методов для работы с настройками. Мы начнем с простого метода доступа, который позволяет указать имя параметра, который нам нужно, а также резервное значение, которое мы можем использовать, если параметр не существует (по дефолту мы будем использовать null):

/**
 * 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;
}

Если вы хотите получить все настройки одновременно, то просто обратитесь к свойству настроек в самой модели пользователя, например, так:

$settings = $user->settings;

Запись настроек в базу данных

Помимо доступа, нам также понадобятся средства для обновления настроек. Однако это не просто перезапись массива. Если бы мы это сделали, то удалили бы все настройки, которых нет в новом массиве, выданном на замену.

Кроме того, разумно предположить, что в приложении есть страница настроек, где вы можете изменить и сохранить несколько настроек одновременно, поэтому для смягчения обеих этих проблем мы будем использовать array_merge, для гарантии, что любые новые настройки будут автоматически добавлены, а любые существующие настройки — обновлены, чтобы использовать их новые значения (при условии, что новое значение предоставляется).

Мы также добавим логический параметр $save, который по умолчанию имеет значение true. Таким образом, вызов метода автоматически сохранит изменения, однако, если вы хотите выполнить дальнейшие операции над моделью User перед сохранением, у вас есть возможность отключить функцию сохранения. Мы будем называть этот метод «settings», поскольку потенциально работаем более чем с одной настройкой:

/**
 * 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;
}

Тестирование

В завершении давайте добавим несколько модульных тестов, которые подтвердят, что методы работают так, как мы ожидаем. Мы проверим, что можно получить доступ к настройке, что можно вернуть дефолтное значению, и что мы можем сохранить измененные настройки в базе данных:

/** @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
    );
}

Завершение

Хотя в Laravel нет встроенных пользовательских настроек, но, как вы увидели в этой статье, добавить такой функционал очень просто, в том числе, сделать дефолтные настройки, автоматическое сохранение и массовое обновление.

Еще раз спасибо и хорошего кода!

Автор: Matt Kingshott
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.