Используем Enum в Laravel

Используем Enum в Laravel

Сегодня я хотел бы обсудить с вами несколько путей, в которых можно использовать такой тип как enums в Laravel. Особенно с той точки зрения, что такого типа как Enum в PHP не существует.

Итак, что же такое enum?

Enum - в программировании тип данных, чьё множество значений представляет собой ограниченный список идентификаторов.

Если вкратце, то Enum - это тип данных для категоризации именованных значений. Они могут использоваться в самых разных местах. Например, вместо того, чтобы жёстко прописывать в коде статусы сообщений в блоге, их можно вынести в отдельный класс Enum.

QuickAdminPanel - сервис по генерации админки на Laravel, написанный Povilas Korop. И если вы в нём прописали поля, которые содержат выпадающие списки, то он их выведет в константы следующим образом:

 

<?php

 

namespace App\Models;

 

 

class User extends Authenticatable

{

    use SoftDeletes, Notifiable, HasFactory;

 

    public $table = 'users';

 

 

    const LANGUAGE_SELECT = [

        'ru_ru' => 'Russian',

        'en_us' => 'English',

    ];

 

 

    const SMS_DAYS_BEFORE_SELECT = [

        '0'  => 'The same day',

        '1'  => 'Before 1 day',

        '2'  => 'Before 2 days',

        '3'  => 'Before 3 days',

        '7'  => 'Before week',

        '31' => 'Before month',

    ];

 

    const MAIL_DAYS_BEFORE_SELECT = [

        '0'  => 'The same day',

        '1'  => 'Before 1 day',

        '2'  => 'Before 2 days',

        '3'  => 'Before 3 days',

        '7'  => 'Before week',

        '31' => 'Before month',

    ];

 

}

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

PHP предлагает очень простую имплементацию SPL, но в реальности она не очень сильно помогает. С другой стороны есть очень популярный пакет bensampo/laravel-enum, который я часто использую в своих проектах. Это действительно здорово.

Сейчас мы попробуем его в деле, заменив обычные массивы в выпадающих списках и перенесём их в enums.

Итак, следуя принципам DDD и Porto, создадим родительский абстрактный класс, от которого мы в дальнейшем будем наследоваться:

<?php

 

 

namespace Parents\Enums;

 

 

class Enum extends \BenSampo\Enum\Enum

{

 

}

 

Теперь попробуем сделать наш первый простой enum - выпадающий список поддерживаемых языков:

 

    const LANGUAGE_SELECT = [

        'ru_ru' => 'Russian',

        'en_us' => 'English',

    ];

 

Как вы видите, мы на входе имеем массив, где ключ - это значение выпадающего списка, а значение массива - его текст. Создадим для этого выпадающего списка свой первый класс:

 

<?php

 

 

namespace Domains\Users\Enums;

 

 

use BenSampo\Enum\Contracts\LocalizedEnum;

 

class LanguageEnum extends \Parents\Enums\Enum implements LocalizedEnum

{

    const Russian = 'ru_ru';

    const English = 'en_us';

}

 

Как видите, мы сделали здесь наоборот, в названии контанты мы используем текст выпадающего списка. Это сделано для того, чтобы обойти ограничения в поддержках типов данных. Текст он всегда является строкой. А тип INT мы в названии контанты использовать не можем. А значение списка может хранить в себе в том числе и целочисленный тип.

Итак, как же мы можем использовать этот класс. Например, при создании пользователя в blade мы можем использовать следуюшее:

 

                <select class="form-control {{ $errors->has('language') ? 'is-invalid' : '' }}" name="language" id="language">

                    <option value disabled {{ old('language', null) === null ? 'selected' : '' }}>{{ trans('global.pleaseSelect') }}</option>

                    @foreach(\Domains\Users\Enums\LanguageEnum::getInstances() as $key => $label)

                        <option value="{{ $label->value }}" {{ old('language', 'ru_ru') === (string) $label->value ? 'selected' : '' }}>{{$label->description}}</option>

                    @endforeach

                </select>

 

Для получения всех имеющихся значений мы используем метод \Domains\Users\Enums\LanguageEnum::getInstances(). На выходе мы получаем массив объектов. У этих объектов есть три важных свойства: value, key, description.

В нашем случае value - это значение выпадающего списка. А description - это переведённый текст для отображения на экране. Как же нам перевести значения? Это очень просто. Для этого создадим в папке с переводами resources/lang/en/enums.php файл enums.php:

<?php

use Domains\Users\Enums\LanguageEnum;

use Domains\Users\Enums\SmsDayBefore;

 

return [

    LanguageEnum::class => [

        LanguageEnum::Russian => 'Russian',

        LanguageEnum::English => 'English',

    ],

    SmsDayBefore::class => [

        SmsDayBefore::SAME_DAY => 'The same day',

        SmsDayBefore::BEFORE_ONE => 'Before 1 day',

        SmsDayBefore::BEFORE_TWO => 'Before 2 days',

        SmsDayBefore::BEFORE_THREE => 'Before 3 days',

        SmsDayBefore::BEFORE_WEEK => 'Before week',

        SmsDayBefore::BEFORE_MONTH => 'Before month'

    ],

];

 

Также в blade файле если нам понадобится получить значение списка, мы используем такую конструкцию:

 

{{ \Domains\Users\Enums\LanguageEnum::fromValue($user->language)?->description ?? '' }}

 

Но это лишь вершина айсберга тех преимуществ, которые мы получаем. При обращении к полю language модели User мы можем сразу получать объект класса LanguageEnum. Для этого в User.php нам надо прописать следующее:

 

    protected $casts = [

        'is_admin' => 'boolean',     // Example standard laravel cast

        'user_type' => LanguageEnum::class, // Example enum cast

    ];

 

Далее где-то в контроллеме мы получаем следующее:

 

$user = User::first();

$user->language // Instance of LanguageEnum

 

Как быть с миграциями? И здесь тоже есть очень удобный инструмент:

 

use \Domains\Users\Enums\LanguageEnum;

use Illuminate\Support\Facades\Schema;

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Database\Migrations\Migration;

 

class UpdateUsersTable extends Migration

{

    /**

     * Run the migrations.

     *

     * @return void

     */

    public function up(): void

    {

        Schema::table('users', function (Blueprint $table): void {            

            $table->enum('type', LanguageEnum::getValues())

                ->default(LanguageEnum::Russian);

        });

    }

}

 

И что не менее важно, мы получаем и дополнительные преимущества по валидации:

 

$this->validate($request, [

        'language' => ['required', new EnumValue(LanguageEnum::class)],

    ]);

 

Помните тот массив с ключами в виде int?

 

    const SMS_DAYS_BEFORE_SELECT = [

        '0'  => 'The same day',

        '1'  => 'Before 1 day',

        '2'  => 'Before 2 days',

        '3'  => 'Before 3 days',

        '7'  => 'Before week',

        '31' => 'Before month',

    ];

 

Сделаем из него enum:

<?php

 

 

namespace Domains\Users\Enums;

 

 

class SmsDayBefore extends \Parents\Enums\Enum implements \BenSampo\Enum\Contracts\LocalizedEnum

{

    const SAME_DAY = 0;

    

    const BEFORE_ONE = 1;

    

    const BEFORE_TWO = 2;

    

    const BEFORE_THREE = 3;

    

    const BEFORE_WEEK = 7;

    

    const BEFORE_MONTH = 31;

 

    public static function parseDatabase($value)

    {

        return (int) $value;

    }

}

 

Как видите, мы в значениях храним не строку, а int! И мы хотим, чтобы операции с БД также осуществлялись в формате int. Здесь нам и поможет метод parseDatabase.

 

Этот подход в целом исключает необходимость дублирования кода, даёт больше преимуществ в работе с константами по сравнению с обычными массивами. Я знаю что это всё не идеально и надеюсь, что в будущем мы увидим поддержку enum в php.

 

Популярное

Самые популярные посты

Как быть максимально продуктивным на удалённой работе?
Business

Как быть максимально продуктивным на удалённой работе?

Я запустил собственный бизнес и намеренно сделал всё возможное, чтобы работать из любой точки мира. Иногда я сижу с своём кабинете с большим 27-дюймовым монитором в своей квартире в г. Чебоксары. Иногда я нахожусь в офисе или в каком-нибудь кафе в другом городе.

Привет! Меня зовут Сергей Емельянов и я трудоголик
Business PHP

Привет! Меня зовут Сергей Емельянов и я трудоголик

Я программист. В душе я предприниматель. Я начал зарабатывать деньги с 11 лет, в суровые 90-е годы, сдавая стеклотару в местный магазин и обменивая её на сладости. Я зарабатывал столько, что хватало на разные вкусняшки.

Акция! Профессиональный разработчик CRM за 2000 руб. в час

Выделю время под ваш проект. Знания технологий Vtiger CRM, SuiteCRM, Laravel, Vue.js, Golang, React.js. Предлагаю варианты сотрудничества, которые помогут вам воспользоваться преимуществами внешнего опыта, оптимизировать затраты и снизить риски. Полная прозрачность всех этапов работы и учёт временных затрат. Оплачивайте только рабочие часы разработки после приемки задачи. Экономьте на платежах по его содержанию разработчика в штате. Возможно заключение договора по ИП. С чего начать, чтобы нанять профессионального разработчика на full-time? Просто заполните форму!

Telegram
@sergeyem
Telephone
+4915211100235