Магазин на Laravel 7, часть 9. Панель управления сайтом, авторизация администратора
09.10.2020
Теги: Laravel • MySQL • PHP • Web-разработка • Авторизация • Аутентификация • ИнтернетМагазин • КаталогТоваров • Миграции • ПанельУправления • Пользователь • ПраваДоступа • Практика • Фреймворк
Есть еще один момент, о котором забыл упомянуть в предыдущей части. Если аутентифицированный пользователь попробует перейти на страницу регистрации или на страницу восстановления пароля — он будет перенаправлен на страницу /home
. Это логично, потому что на странице регистрации или восстановления пароля ему делать нечего. Страница /home
нам не нужна, поэтому мы удалили контроллер HomeController
, шаблон views/home.blade.php
и маршрут до этой страницы — но редирект остался. Изменить это можно в посреднике (middleware) RedirectIfAuthenticated
.
namespace App\Http\Middleware; use App\Providers\RouteServiceProvider; use Closure; use Illuminate\Support\Facades\Auth; class RedirectIfAuthenticated { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { // return redirect(RouteServiceProvider::HOME); return redirect()->route('user.index'); } return $next($request); } }
Администратор магазина
Хорошо, с этим разобрались, двигаемся дальше. Теперь нам нужна панель администратора магазина, где можно будет управлять каталогом и обрабатывать заказы. Администратор — тоже пользователь сайта и должен быть зарегистрирован и аутентифицирован. Чтобы различать обычных пользователй и администратора, добавим еще одно поле admin
в таблицу базы данных users
.
> php artisan make:migration alter_users_table --table=users
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AlterUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->boolean('admin')->after('email')->default(false); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('admin'); }); } }
> php artisan migrate
Теперь создадим контроллер, который будет отвечать за показ главной страницы панели управления:
> php artisan make:controller Admin\AdminController --invokable
namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class IndexController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('auth'); $this->middleware('admin'); } /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function __invoke(Request $request) { return view('admin.index'); } }
Обратите внимание, что контроллер мы создали в директории app/Http/Controllers/Admin
— это значит, что при добавлении новых маршртутов, мы должны указать пространство имен (относительно дефолтного App\Http\Controllers
).
// это первый вариант указания пространства имен Route::name('admin.')->prefix('admin')->group(function () { Route::get('index', 'Admin\IndexController')->name('index'); });
// это второй вариант указания пространства имен Route::namespace('Admin')->name('admin.')->prefix('admin')->group(function () { Route::get('index', 'IndexController')->name('index'); });
Теперь нам нужны два шаблона — layout-шаблон admin.blade.php
создадим в директории views/layout
, а шаблон главной страницы панели управления index.blade.php
— в директории views/admin
.
<!doctype html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Панель управления</title> <link rel="stylesheet" href="{{ asset('css/app.css') }}"> <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/> <link rel="stylesheet" href="{{ asset('css/admin.css') }}"> <script src="{{ asset('js/app.js') }}"></script> <script src="{{ asset('js/admin.js') }}"></script> </head> <body> <div class="container"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <!-- Бренд и кнопка «Гамбургер» --> <a class="navbar-brand" href="{{ route('admin.index') }}">Панель управления</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-example" aria-controls="navbar-example" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <!-- Основная часть меню (может содержать ссылки, формы и прочее) --> <div class="collapse navbar-collapse" id="navbar-example"> <!-- Этот блок расположен слева --> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="#">Заказы</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Каталог</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Категории</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Бренды</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Товары</a> </li> </ul> <!-- Этот блок расположен справа --> <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a onclick="document.getElementById('logout-form').submit(); return false" href="{{ route('user.logout') }}" class="nav-link">Выйти</a> </li> </ul> <form id="logout-form" action="{{ route('user.logout') }}" method="post" class="d-none"> @csrf </form> </div> </nav> <div class="row"> <div class="col-12"> @if ($message = Session::get('success')) <div class="alert alert-success alert-dismissible mt-0" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть"> <span aria-hidden="true">×</span> </button> {{ $message }} </div> @endif @yield('content') </div> </div> </div> </body> </html>
@extends('layout.admin') @section('content') <h1>Панель управления</h1> <p>Добро пожаловать, {{ auth()->user()->name }}</p> <p>Это панель управления для администратора интернет-магазина.</p> <form action="{{ route('user.logout') }}" method="post"> @csrf <button type="submit" class="btn btn-primary">Выйти</button> </form> @endsection
В конструкторе контроллера мы используем два посредника — auth
и admin
. Первый проверяет, что пользователь аутентифицирован, а второй — что пользователь является администратором. Эти имена должны быть определены в классе Kernel
(файл app/Http/Kernel.php
). Имя auth
уже есть, так что добавим admin
.
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { /** * 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, 'admin' => \App\Http\Middleware\Administrator::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, ]; }
И создадим класс посредника Administrator
:
> php artisan make:middleware Administrator
namespace App\Http\Middleware; use Closure; class Administrator { public function handle($request, Closure $next, $guard = null) { // если это не администратор — показываем 404 Not Found if ( ! auth()->user()->admin) { abort(404); } return $next($request); } }
Обычный пользователь после входа направляется на страницу /user/index
. Но администратора мы должны отправить на страницу /admin/index
. Мы можем это сделать в контроллере LoginController
.
class LoginController extends Controller { /** * Сразу после входа выполняем редирект и устанавливаем flash-сообщение */ protected function authenticated(Request $request, $user) { $route = 'user.index'; $message = 'Вы успешно вошли в личный кабинет'; if ($user->admin) { $route = 'admin.index'; $message = 'Вы успешно вошли в панель управления'; } return redirect()->route($route) ->with('success', $message); } }
Для панели управления нам потребуется несколько контроллеров. Неудобно в конструкторе каждого из них прописывать два посредника auth
и admin
. Давайте уберем конструктор в контроллере Admin\IndexController
, а посредники пропишем в маршруте.
// первый способ добавления посредников Route::namespace('Admin')->name('admin.')->prefix('admin')->middleware('auth', 'admin')->group(function () { Route::get('index', 'IndexController')->name('index'); });
// второй способ добавления посредников Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллера 'middleware' => ['auth', 'admin'] // один или несколько посредников ], function () { Route::get('index', 'IndexController')->name('index'); });
Товар в корзине
Неудобно, что корзина в правом верхнем углу всегда одинаковая — когда в ней есть товар(ы) и когда она пустая. Давайте это исправим, чтобы корзина с товаром была другого цвета. Для этого в классе ComposerServiceProvider
будем передавать в layout-шаблон site.blade.php
переменную $positions
.
class ComposerServiceProvider extends ServiceProvider { /* ... */ public function boot() { View::composer('layout.part.roots', function($view) { $view->with(['items' => Category::roots()]); }); View::composer('layout.part.brands', function($view) { $view->with(['items' => Brand::popular()]); }); View::composer('layout.site', function($view) { $view->with(['positions' => Basket::getBasket()->products->count()]); }); } }
Для этого в модели Basket
нам потребуется метод getBasket()
, который будет возвращать объект корзины:
namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Cookie; class Basket extends Model { /* ... */ public static function getBasket() { $basket_id = request()->cookie('basket_id'); if (!empty($basket_id)) { try { $basket = Basket::findOrFail($basket_id); } catch (ModelNotFoundException $e) { $basket = Basket::create(); } } else { $basket = Basket::create(); } Cookie::queue('basket_id', $basket->id, 525600); return $basket; } }
А в контроллере BasketController
метод getBasket()
можно удалить, получая объект корзины в конструкторе:
namespace App\Http\Controllers; use App\Basket; use Illuminate\Http\Request; class BasketController extends Controller { private $basket; public function __construct() { $this->basket = Basket::getBasket(); } /* ... */ }
Теперь осталось только в layout-шаблоне показать кол-во позиций в корзине и выделить ее цветом:
<ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link @if ($positions) text-success @endif" href="{{ route('basket.index') }}"> Корзина @if ($positions) ({{ $positions }}) @endif </a> </li> @guest <li class="nav-item"> <a class="nav-link" href="{{ route('user.login') }}">Войти</a> </li> @if (Route::has('user.register')) <li class="nav-item"> <a class="nav-link" href="{{ route('user.register') }}">Регистрация</a> </li> @endif @else <li class="nav-item"> <a class="nav-link" href="{{ route('user.index') }}">Личный кабинет</a> </li> @endif </ul>
Как-то не слишком удачно у меня получилось с корзиной. Каждый раз, когда новый посетитель приходит на сайт — создается новая запись в таблице БД baskets
. Хотя в реальности 90% посетителей не станут покупателями. Так что добавил еще один метод getCount()
в модель Basket
.
class Basket extends Model { /** * Возвращает количество позиций в корзине */ public static function getCount() { $basket_id = request()->cookie('basket_id'); if (empty($basket_id)) { return 0; } return self::getBasket()->products->count(); } }
class ComposerServiceProvider extends ServiceProvider { /* ... */ public function boot() { /* ... */ View::composer('layout.site', function($view) { $view->with(['positions' => Basket::getCount()]); }); } }
- Магазин на Laravel 7, часть 8. Регистрация и аутентификация пользователей на сайте
- Мини-блог на Laravel, часть 9. Защита маршрутов создания, редактирования и удаления
- Магазин на Laravel 7, часть 21. Добавляем профили и используем их при оформлении заказа
- Магазин на Laravel 7, часть 18. Панель управления, пользователи и CRUD страниц сайта
- Магазин на Laravel 7, часть 17. Панель управления, работа с заказами, изменение статуса
- Магазин на Laravel 7, часть 16. Панель управления, CRUD-операции для товаров каталога
- Магазин на Laravel 7, часть 15. Панель управления, добавление и редактирование брендов
Поиск: Laravel • MySQL • PHP • Web-разработка • Авторизация • Аутентификация • Интернет магазин • Каталог товаров • Миграции • Панель управления • Пользователь • Права доступа • Практика • Фреймворк