Слава Україні!

Laravel. Роли и Права. Часть 2

Laravel. Роли и Права. Подготовка моделей, отношений, трайт Laravel

Отношения Ролей и Прав

В этой части мы настроим отношения Ролей и Прав. Для Пользователей мы добавим отношения в следующем разделе. Откройте файл модели Role.php и добавьте отношение belongsToMany.


class Role extends Model
{
    public function permissions()
    {
        return $this->belongsToMany(Permission::class,'roles_permissions');
    }
}

Откройте файл модели Permission.php и скопируйте в него.


class Permission extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class,'roles_permissions');
    }
}

Так мы определили отношения «Многие ко Многим» между Ролями и Правами.

Трейт HasRolesAndPermissions для модели User

Теперь займемся моделью User. Пользователь может иметь много Прав и много Ролей. То же самое наоборот, Роль может иметь много Пользователей, а Право может иметь много Пользователей. Поэтому нам нужно создать отношение «Многие ко Многим» в модели User.

Для чистоты кода я создам эти отношения в трейте, а затем использую его в модели User. Мы также можем использовать этот трейт позже, если добавим в наше приложение какую-либо модель, требующую Роли и Права.

В папке app создайте новую папку и назовите ее «Traits». Создайте в ней файл и назовите его HasRolesAndPermissions.php.

Скопируйте в него следующий код.


namespace App\Traits;

use App\Models\Role;
use App\Models\Permission;

trait HasRolesAndPermissions
{
    /**
     * @return mixed
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class,'users_roles');
    }
    /**
     * @return mixed
     */
    public function permissions()
    {
        return $this->belongsToMany(Permission::class,'users_permissions');
    }
}

Мы задали отношения roles и permissions в соответствующих моделях. Теперь для того, чтобы использовать этот трейт в вашей модели User, откройте файл User.php и добавьте в него использование трейта HasRolesAndPermissions:


use App\Traits\HasRolesAndPermissions;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable, HasRolesAndPermissions; // Наш новый трейт
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
Пользователь hasRole

Чтобы проверить, есть ли у текущего залогиненного Пользователя Роль, мы добавим новую функцию в трейт HasRolesAndPermissions. Откройте этот файл трейта и добавьте в него эту функцию.


/**
 * @param mixed ...$roles
 * @return bool
 */
public function hasRole(... $roles ) {
    foreach ($roles as $role) {
        if ($this->roles->contains('slug', $role)) {
            return true;
        }
    }
    return false;
}

В функцию мы передаем массив $roles и проверяем в цикле, содержат ли роли текущего пользователя заданную роль.

Пользователь hasPermission

Для проверки прав доступа текущего пользователя, мы добавим два нижеприведенных метода в наш трейт HasRolesAndPermissions.


/**
 * @param $permission
 * @return bool
 */
public function hasPermission($permission)
{
    return (bool) $this->permissions->where('slug', $permission)->count();
}
/**
 * @param $permission
 * @return bool
 */
public function hasPermissionTo($permission)
{
    return $this->hasPermission($permission);
}

Метод проверяет, содержат ли права пользователя заданное право, если да, то тогда он вернет true, а иначе false.

Пользователь hasPermissionThroughRole

Как мы знаем, у нас между Ролями и Правами есть отношение «Многие ко Многим». Это позволяет нам проверять, есть ли у Пользователя Права через его Роль. Чтобы это реализовать, мы добавим новую функцию в наш трейт HasRolesAndPermissions.


/**
 * @param $permission
 * @return bool
 */
public function hasPermissionThroughRole($permission)
{
    foreach ($permission->roles as $role){
        if($this->roles->contains($role)) {
            return true;
        }
    }
    return false;
}

Эта функция проверяет, привязана ли Роль с Правами к Пользователю. Метод hasPermissionTo() проверит эти два условия.

Обновите метод hasPermissionTo, как показано ниже.


/**
 * @param $permission
 * @return bool
 */
public function hasPermissionTo($permission)
{
   return $this->hasPermissionThroughRole($permission) || $this->hasPermission($permission->slug);
}

Теперь у нас есть метод, который будет проверять, есть ли у Пользователя Права напрямую или через Роль. Позже мы будем использовать этот метод для добавления кастомной blade-директивы.

Выдача Прав

Предположим, что мы хотим прикрепить некоторые Права к текущему Пользователю. Для этого мы добавим новый метод в трейт HasRolesAndPermissions.


/**
 * @param array $permissions
 * @return mixed
 */
public function getAllPermissions(array $permissions)
{
    return Permission::whereIn('slug',$permissions)->get();
}

/**
 * @param mixed ...$permissions
 * @return $this
 */
public function givePermissionsTo(... $permissions)
{
    $permissions = $this->getAllPermissions($permissions);
    if($permissions === null) {
        return $this;
    }
    $this->permissions()->saveMany($permissions);
    return $this;
}

Первый метод получает все Права на основе переданного массива. Во второй функции мы передаем Права в виде массива и получаем все Права из базы данных на основе массива.

Далее мы используем метод permissions() для вызова метода saveMany(), чтобы сохранить разрешения для текущего пользователя.

Удаление Прав

Чтобы удалить Права Пользователя, мы передаем Права методу deletePermissions() и удаляем все прикрепленные Права с помощью метода detach().


/**
 * @param mixed ...$permissions
 * @return $this
 */
public function deletePermissions(... $permissions )
{
    $permissions = $this->getAllPermissions($permissions);
    $this->permissions()->detach($permissions);
    return $this;
}

/**
 * @param mixed ...$permissions
 * @return HasRolesAndPermissions
 */
public function refreshPermissions(... $permissions )
{
    $this->permissions()->detach();
    return $this->givePermissionsTo($permissions);
}

Второй метод фактически удаляет все Права Пользователя, а затем переназначает предоставленные для него Права.

Проверьте Права и Роли Пользователя, как показано ниже.


$user = App\Models\User::find(1);
dd($user->hasRole('web-developer')); //вернёт true
dd($user->hasRole('project-manager')); //вернёт false
dd($user->givePermissionsTo('manage-users')); //выдаём разрешение
dd($user->hasPermission('manage-users')); //вернёт true

Добавление blade-директивы для Ролей и Прав

Создадим директиву, которой будем пользоваться в blade-шаблонах. Для начала мы создадим нового сервис-провайдера.


php artisan make:provider RolesServiceProvider

Не забудьте добавить RolesServiceProvider в список providers в файле config/app.php. Откроем свежесозданный RolesServiceProvider и обновим его с помощью приведенного ниже кода.


namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class RolesServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Blade::directive('role', function ($role){
            return "<?php if(auth()->check() && auth()->user()->hasRole({$role})): ?>";
        });
        Blade::directive('endrole', function ($role){
            return "<?php endif; ?>";
        });
    }
}

В сервис провайдере мы объявляем кастомную директиву, используя фасад Blade. В первой директиве мы проверяем, прошел ли Пользователь аутентификацию и имеет ли он заданную роль. Во второй директиве — закрываем оператор if.

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


@role('project-manager')
 Project Manager Panel
@endrole 
@role('web-developer')
 Web Developer Panel
@endrole

До сих пор мы использовали Роли в нашей директиве. Для Прав мы будем использовать директиву can, чтобы проверить, есть ли у Пользователя Право. Вместо использования $user->hasPermissionTo() мы будем использовать Gate::allows('manage-users').

Для достижения этой функциональности мы создадим нового сервис провайдера и назовем его PermissionServiceProvider.


php artisan make:provider PermissionServiceProvider

Не забудьте добавить PermissionServiceProvider в список providers в файле config/app.php. Откройте его и обновите его с помощью приведенного ниже кода.


namespace App\Providers;

use App\Models\Permission;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class PermissionServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        try {
            Permission::get()->map(function ($permission) {
                Gate::define($permission->slug, function ($user) use ($permission) {
                    return $user->hasPermissionTo($permission);
                });
            });
        } catch (\Exception $e) {
            report($e);
            return false;
        }
    }
}

Здесь мы сопоставляем все Права, определяем slug Права (в нашем случае) и проверяем, есть ли у Пользователя Право. Теперь вы можете проверить Права Пользователя, как показано ниже.


// вернёт true для текущего пользователя, 
// если ему дано право управлять пользователями
Gate::allows('manage-users');

Добавление Мидлвара для Ролей и Прав

Мы можем создать специфичные Ролевые области в веб-приложении. Например, можно предоставить доступ для управления Пользователями только Менеджерам проекта. Для этого мы будем использовать Laravel мидлвары. Используя их, мы можем добавить дополнительный контроль над входящими запросами.

Чтобы создать мидлвар для Ролей, выполните команду ниже.


php artisan make:middleware RoleMiddleware

Откройте созданный класс RoleMiddleware и скопируйте в него.


namespace App\Http\Middleware;

use Closure;

class RoleMiddleware
{
    /**
     * Handle an incoming request.
     * @param $request
     * @param Closure $next
     * @param $role
     * @param null $permission
     * @return mixed
     */
    public function handle($request, Closure $next, $role, $permission = null)
    {
        if(!auth()->user()->hasRole($role)) {
            abort(404);
        }
        if($permission !== null && !auth()->user()->can($permission)) {
            abort(404);
        }
        return $next($request);
    }
}

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

Перед использованием этого мидлвара вы должны добавить его в файл App\Http\Kernel.php.

Обновите массив $routeMiddleware как показано ниже.


 /**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array
 */
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,

    'role'  =>  \App\Http\Middleware\RoleMiddleware::class, // наш мидлвар роли
];

Теперь вы можете использовать мидлвар, как показано ниже.


Route::group(['middleware' => 'role:web-developer'], function() {
   Route::get('/dashboard', function() {
      return 'Добро пожаловать, Веб-разработчик';
   });
});
Предыдущая заметка Laravel. Роли и Права. Часть 1
Следующая заметка Git - 10 полезных команд

Важлива інформація

Міністерство оборони
України
з 24.02 по 21.04
втрати противника
орієнтовно склали:
459530 ( +950 ) особового складу
7229 ( +16 ) танків
13896 ( +23 ) бойових бронемашин
11719 ( +41 ) артилерійських систем
1046 ( +0 ) РСЗВ
767 ( +4 ) засоби ППО
348 ( +0 ) літаків
325 ( +0 ) гелікоптерів
15785 ( +68 ) автомобільної техніки
26 ( +0 ) кораблі / катери
2115 ( +6 ) крилаті ракети
9379 ( +38 ) БПЛА
1926 ( +8 ) спец. техніки
4 ( +0 ) установок ОТРК/ТРК
1 ( +0 ) war.submarines

Поиск