«Так… мне нужна простая авторизация. Какая-нибудь админская роль, и может роль редактора/модератора. Сейчас погуглим. О! Для laravel уже есть готовые пакеты! zizaco/entrust, spatie/laravel-permission и другие! Давай выберем какой-нибудь!»

Примерно так все и происходит. Потом миграция пакета добавляет в базу 5 табличек для хранения ролей, пермишенов и их отношений. Все правила авторизации, такие как роли 'admin' и 'editor' могут делать 'edit posts', хранятся в этих таблицах. Обычно в проекте много копий базы данных. Копии разработчиков, тестовая база(ы) и продакшен. В итоге все эти правила авторизации вынуждены синхронизироваться между базами данных.

Я встречал пару проектов где была одна главная копия правил в базе данных продакшена и остальные все копировали оттуда.

Идея использование Seeder класса(пример) намного лучше. Нужно только запустить

php artisan db:seed AuthSeeder

и у вас свежая версия правил в базе данных. Таким образом, этот seeder класс становится неким Single Source Of Truth. Хорошо, но в этом подходе все еще много неудобств:

  • Seeder должен быть довольно умным, чтобы не просто создавать роли и пермишены и связи между ними, но и синхронизировать старые версии. Т.е. удалить или создать связи между ролями и пермишенами если надо.
  • Правила хранятся в базе и необходима постоянная синхронизация между ними. Каждое изменение требования вида «редакторы не должны теперь редактировать посты, только публиковать их» приводит к изменению в seeder'е, сихронизации кодобазы через гит или как-нибудь, и «НЕ ЗАБЫТЬ ЗАПУСТИТЬ AuthSeeder!»
  • Правила авторизации могут быть сложными. Например публикация может быть отредактирована не только редакторами или админами, но и автором этой публикации. Поэтому вместо простого middleware:

['middleware' => ['permission:edit posts']]

разработчики должны использовать стандартную авторизацию laravel:

class PostPolicy
{
    public function edit(User $user, Post $post)
    {
         return $user->id == $post->owner_id 
             || $user->hasPermissionTo('edit posts');
    }
}

Так может самый простой путь не самый лучший?


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

Попробуем проанализировать что обычно проектам нужно? Простая ролевая система. Почти всегда одна роль на юзера. Администратор. Ну может еще редактор или модератор. И эти роли имеют некие пермишены. Пойдем самым прямым путем! Добавим новое поле в таблицу users! is_admin или role. Потом пару методов-хелперов в класс User:

class User
{
    public function isAdmin(): bool {...}
    public function isEditor(): bool {...}
}

Теперь пермишены. Laravel предоставляет два основных метода их описания: Gates и Policies. Я буду использовать gates(тут еще небольшой трюк с функцией в переменной, но для любителей функционального программирования это и не трюк вовсе):

    $isAdmin = function (User $user) {
        return $user->isAdmin();
    }

    $isEditorOrAdmin = function (User $user) {
        return $user->isAdmin() || $user->isEditor();
    }

    Gate::define('foo-permission', $isAdmin);
    Gate::define('bar-permission', $isAdmin);

    Gate::define('editor-permission', $isEditorOrAdmin);

    // Complex permission
    Gate::define('edit-post', function(User $user, Post $post) {
         return $user->id == $post->owner_id 
            || $user->isAdmin();
    });

Если проекту нужно несколько ролей на пользователя, то просто добавляем таблицу user_roles и изменяем методы-хелперы класса User. Содержимое seeder класса для *trust пакетов и этой code-based авторизации практически идентично! Но правила теперь просто хранятся в коде и нет необходимости постоянно синхронизировать их в базах данных.

Я не хочу сказать, что эти пакеты бесполезны. Этот подход весьма полезен в проектах со сложной системой авторизации, где клиент сам хочет настраивать роли впоследствии. А еще есть проекты с динамическими пермишенами. Пример: форум с подфорумами. Каждый подфорум может иметь своих модераторов, каждый модератор обладает определенными администратором правами на этом подфоруме. Еще пример — telegram и его группы. Там тоже самое. Таким проектам действительно необходима сложная, хранимая в базе данных, система авторизации со всеми ее проблемами. Но большинству других — нет.

Ситуация с пакетами для laravel становится похожей на ситуацию с компонентами для Delphi(старички помнят). Пакеты ставят не раздумывая — нужны реально или нет.

Так, мне бы тут в своем проекте посчитать $a + $b. Нет ли какого-нибудь laravel-sum пакета?

P.S. Прошу прощения за «пермишены», но хорошего точного перевода я не нашел.

Комментарии (12)


  1. AEP
    17.10.2018 22:05
    +3

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


  1. 1ax3l
    18.10.2018 08:23
    -1

    Чем отличается ваш пост от этого?


    1. Adelf Автор
      18.10.2018 08:25
      +1

      Я не открываю gates & policies, а просто намекаю, что можно пользоваться ими безо всяких доп пакетов. В статье по вашей линке, они юзаются параллельно. Посыл совсем другой.


  1. UksusoFF
    18.10.2018 09:28

    Во всех этих пакетах печально то что нельзя пермишенны для анонимусов.
    Хотя вроде с выходом 5.7 могло что-то поменятся.


  1. avraam_linkoln
    18.10.2018 09:56
    +1

    Часто пишут по старинке. Типа, как два года назад использовал какую то стороннюю библиотеку, так и дальше на новых проектах ее продолжают использовать, несмотря на то что фреймвоорк уже предлагает лучшие решения из коробки. Но это же разбираться надо, а многим лень


  1. mykolaim
    18.10.2018 11:28
    +1

    Идея использование Seeder класса(пример) намного лучше.


    А почему вы не расматривате вариант вынести создание ролей в миграцию? При таком подходе у всех разработчиков будет актуальное состояние данных в БД.


    1. Adelf Автор
      18.10.2018 11:32

      Это, кстати, хороший вариант :) но немного холиварный. Не по себе мне миграции использовать не для изменения структуры бд. Но, возможно, я не прав.


      1. JhaoDa
        18.10.2018 12:03

        Сиды предназначены только для тестовых данных. Решение в виде миграция очень даже нормальное, особенно с учётом того, что альтернативой является запиливание минимального интерфейса для управления всей авторизационной кухней, даже если это просто создание новой роли.
        Я юзаю миграции для подобного рода «фундаментальных» данных.


        1. Adelf Автор
          18.10.2018 13:01

          Ну… прям опровергнуть полностью ваш подход мне нечем. всякие SRP приплетать тут не очень умно наверно. Могу лишь сказать, что один из пакетов неявно рекомендует именно сиды — github.com/spatie/laravel-permission#database-seeding


          1. JhaoDa
            18.10.2018 13:06
            +1

            Адептусы Святого Шпателя не есть истина в последней инстанции, ибо Великая Книга прямо говорит нам:


            Laravel includes a simple method of seeding your database with test data using seed classes.


            1. Adelf Автор
              18.10.2018 13:16

              Могу лишь согласиться. Так написано прямо начиная с 4.2, я проверил :)
              А мы в своих проектах в миграциях держали лишь структурные изменения. В сидах же лежали умные классы, которые любые старые наборы изначальных данных приводили к нужному свежему набору.
              Так удобнее… поскольку сид можно было менять. а вот миграцию будь добр каждый раз создай новую.
              Похоливарить можно, но нет смысла.


  1. hzone
    18.10.2018 15:09

    По мне, так policy за глаза хватет, когда 1 пользователь = 1 роль.

    Для чего-то сложного, типа роли+права, по хорошему надо писать пакет под конкретный проект (в принципе ничего сложного), — на то и лара.

    Выскажу своёё фи с точки зрения «из коробки» — из коробки оно хорошо в 2х случаях:
    а) оно вписывается в архитектуру приложения и будет востребовано (основной параметр)
    б) вы «буржуй» и думаете в ИХ ключе понимания строения вселенной. Поясню. Русскоговорящие (основной язык архитектора — Русский) думают несколько сложнее буржуев, так как сам язык сложнее. В следствии чего решения более простые, масштабируемые (например пакет настолько удобный, что переносится от проекта к проекту; смотрим на Сову и удивляемся её живучести), и либо используют механику (в данном случае лары) либо вообще настолько простые, что выдают требуемый результат без использования той же лары, средствами основного языка.

    Как-то…