Магазин на Laravel 7, часть 18. Панель управления, пользователи и CRUD страниц сайта
26.10.2020
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • ИнтернетМагазин • КаталогТоваров • ПанельУправления • Практика • Фреймворк • ШаблонСайта
Работа с пользователями
Нужно еще предоставить администратору возможность работы с пользователями. Так что организуем просмотр списка и добавим форму для изменения данных пользователя. Страницу просмотра данных пользователя делать не будем, потому как данных-то всего две строки — «Имя, Фамилия» и «Адрес почты». При редактировании будет возможность изменить пароль пользователя. Не уверен, что это нужно — но пусть будет для полноты картины — в конце концов, это учебный проект.
Я князь! Чего хочу — того и ворочу… эээ… действую в интересах державы.
Мультфильм «Илья Муромец и Соловей Разбойник»
Создаем ресурсный контроллер:
> php artisan make:controller Admin/UserController --resource --model=Models/User
<?php namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; class UserController extends Controller { /** * Показывает список всех пользователей * * @return \Illuminate\Http\Response */ public function index() { $users = User::paginate(5); return view('admin.user.index', compact('users')); } /** * Показывает форму для редактирования пользователя * * @param \App\Models\User $user * @return \Illuminate\Http\Response */ public function edit(User $user) { return view('admin.user.edit', compact('user')); } /** * Обновляет данные пользователя в базе данных * * @param \Illuminate\Http\Request $request * @param \App\Models\User $user * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Validation\ValidationException */ public function update(Request $request, User $user) { /* * Проверяем данные формы */ $this->validator($request->all(), $user->id)->validate(); /* * Обновляем пользователя */ if ($request->change_password) { // если надо изменить пароль $request->merge(['password' => Hash::make($request->password)]); $user->update($request->all()); } else { $user->update($request->except('password')); } /* * Возвращаемся к списку */ return redirect() ->route('admin.user.index') ->with('success', 'Данные пользователя успешно обновлены'); } /** * Возвращает объект валидатора с нужными нам правилами * * @param array $data * @return \Illuminate\Contracts\Validation\Validator|\Illuminate\Validation\Validator */ private function validator(array $data, int $id) { $rules = [ 'name' => [ 'required', 'string', 'max:255' ], 'email' => [ 'required', 'string', 'email', 'max:255', // проверка на уникальность email, исключая // этого пользователя по идентифкатору 'unique:users,email,'.$id.',id', ], ]; if (isset($data['change_password'])) { $rules['password'] = ['required', 'string', 'min:8', 'confirmed']; } return Validator::make($data, $rules); } }
Добавляем маршруты в файл web.php
:
Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллера 'middleware' => ['auth', 'admin'] // один или несколько посредников ], function () { // главная страница панели управления Route::get('index', 'IndexController')->name('index'); // CRUD-операции над категориями каталога Route::resource('category', 'CategoryController'); // CRUD-операции над брендами каталога Route::resource('brand', 'BrandController'); // CRUD-операции над товарами каталога Route::resource('product', 'ProductController'); // доп.маршрут для показа товаров категории Route::get('product/category/{category}', 'ProductController@category') ->name('product.category'); // просмотр и редактирование заказов Route::resource('order', 'OrderController', ['except' => [ 'create', 'store', 'destroy' ]]); // просмотр и редактирование пользователей Route::resource('user', 'UserController', ['except' => [ 'create', 'store', 'show', 'destroy' ]]); });
Шаблон для просмотра списка views/admin/user/index.blade.php
:
@extends('layout.admin', ['title' => 'Все пользователи']) @section('content') <h1 class="mb-4">Все пользователи</h1> <table class="table table-bordered"> <tr> <th>#</th> <th width="25%">Дата регистрации</th> <th width="25%">Имя, фамилия</th> <th width="25%">Адрес почты</th> <th width="20%">Кол-во заказов</th> <th><i class="fas fa-edit"></i></th> </tr> @foreach($users as $user) <tr> <td>{{ $user->id }}</td> <td>{{ $user->created_at->format('d.m.Y H:i') }}</td> <td>{{ $user->name }}</td> <td><a href="mailto:{{ $user->email }}">{{ $user->email }}</a></td> <td>{{ $user->orders->count() }}</td> <td> <a href="{{ route('admin.user.edit', ['user' => $user->id]) }}"> <i class="far fa-edit"></i> </a> </td> </tr> @endforeach </table> {{ $users->links() }} @endsection
Шаблон для редактирования views/admin/user/index.blade.php
:
@extends('layout.admin', ['title' => 'Редактирование пользователя']) @section('content') <h1 class="mb-4">Редактирование пользователя</h1> <form method="post" action="{{ route('admin.user.update', ['user' => $user->id]) }}"> @csrf @method('PUT') <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Имя, Фамилия" required maxlength="255" value="{{ old('name') ?? $user->name ?? '' }}"> </div> <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? $user->email ?? '' }}"> </div> <div class="form-group form-check"> <input type="checkbox" class="form-check-input" name="change_password" id="change_password"> <label class="form-check-label" for="change_password"> Изменить пароль пользователя </label> </div> <div class="form-group"> <input type="text" class="form-control" name="password" maxlength="255" placeholder="Новый пароль" value=""> </div> <div class="form-group"> <input type="text" class="form-control" name="password_confirmation" maxlength="255" placeholder="Пароль еще раз" value=""> </div> <div class="form-group"> <button type="submit" class="btn btn-success">Сохранить</button> </div> </form> @endsection
Преобразование даты регистрации из UTC в Europe/Moscow:
namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Carbon; class User extends Authenticatable { /** * Преобразует дату и время регистрации пользователя из UTC в Europe/Moscow * * @param $value * @return \Carbon\Carbon|false */ public function getCreatedAtAttribute($value) { return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone('Europe/Moscow'); } /** * Преобразует дату и время обновления пользователя из UTC в Europe/Moscow * * @param $value * @return \Carbon\Carbon|false */ public function getUpdatedAtAttribute($value) { return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone('Europe/Moscow'); } }
Страницы сайта
На сайте нужны страницы типа «Доставка», «Оплата», «Контакты». И у администратора должна быть возможность такие страницы создавать, редактировать и удалять. Давайте создадим еще одну таблицу базы данных pages
и ресурсный контроллер для CRUD-операций над страницами.
> php artisan make:model Models/Page -m
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreatePagesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('pages', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('parent_id')->nullable(false)->default(0); $table->string('name', 100)->nullable(false); $table->text('content')->nullable(false); $table->string('slug', 100)->unique()->nullable(false); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('pages'); } }
> php artisan migrate
Создаем ресурсный контроллер:
> php artisan make:controller Admin/PageController --resource --model=Models/Page
namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Page; use Illuminate\Http\Request; class PageController extends Controller { /** * Показывает список всех страниц * * @return \Illuminate\Http\Response */ public function index() { $pages = Page::all(); return view('admin.page.index', compact('pages')); } /** * Показывает форму для создания страницы * * @return \Illuminate\Http\Response */ public function create() { $parents = Page::where('parent_id', 0)->get(); return view('admin.page.create', compact('parents')); } /** * Сохраняет новую страницу в базу данных * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:100', 'parent_id' => 'required|regex:~^[0-9]+$~', 'slug' => 'required|max:100|unique:pages|regex:~^[-_a-z0-9]+$~i', 'content' => 'required', ]); $page = Page::create($request->all()); return redirect() ->route('admin.page.show', ['page' => $page->id]) ->with('success', 'Новая страница успешно создана'); } /** * Показывает информацию о странице сайта * * @param \App\Models\Page $page * @return \Illuminate\Http\Response */ public function show(Page $page) { return view('admin.page.show', compact('page')); } /** * Показывает форму для редактирования страницы * * @param \App\Models\Page $page * @return \Illuminate\Http\Response */ public function edit(Page $page) { $parents = Page::where('parent_id', 0)->get(); return view('admin.page.edit', compact('page', 'parents')); } /** * Обновляет страницу (запись в таблице БД) * * @param \Illuminate\Http\Request $request * @param \App\Models\Page $page * @return \Illuminate\Http\Response */ public function update(Request $request, Page $page) { $this->validate($request, [ 'name' => 'required|max:100', 'parent_id' => 'required|regex:~^[0-9]+$~|not_in:'.$page->id, 'slug' => 'required|max:100|unique:pages,slug,'.$page->id.',id|regex:~^[-_a-z0-9]+$~i', 'content' => 'required', ]); $page->update($request->all()); return redirect() ->route('admin.page.show', ['page' => $page->id]) ->with('success', 'Страница была успешно отредактирована'); } /** * Удаляет страницу (запись в таблице БД) * * @param \App\Models\Page $page * @return \Illuminate\Http\Response */ public function destroy(Page $page) { if ($page->children->count()) { return back()->withErrors('Нельзя удалить страницу, у которой есть дочерние'); } $page->delete(); return redirect() ->route('admin.page.index') ->with('success', 'Страница сайта успешно удалена'); } }
Добавляем маршруты в файл web.php
:
Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллера 'middleware' => ['auth', 'admin'] // один или несколько посредников ], function () { // главная страница панели управления Route::get('index', 'IndexController')->name('index'); // CRUD-операции над категориями каталога Route::resource('category', 'CategoryController'); // CRUD-операции над брендами каталога Route::resource('brand', 'BrandController'); // CRUD-операции над товарами каталога Route::resource('product', 'ProductController'); // доп.маршрут для показа товаров категории Route::get('product/category/{category}', 'ProductController@category') ->name('product.category'); // просмотр и редактирование заказов Route::resource('order', 'OrderController', ['except' => [ 'create', 'store', 'destroy' ]]); // просмотр и редактирование пользователей Route::resource('user', 'UserController', ['except' => [ 'create', 'store', 'show', 'destroy' ]]); // CRUD-операции над страницами сайта Route::resource('page', 'PageController'); });
Добавляем свойство $fillable
в модель, чтобы использовать «mass assignment» и настраиваем связи между таблицами:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Page extends Model { protected $fillable = [ 'name', 'slug', 'content', 'parent_id', ]; /** * Связь «один ко многим» таблицы `pages` с таблицей `pages` * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function children() { return $this->hasMany(Page::class, 'parent_id'); } /** * Связь «страница принадлежит» таблицы `pages` с таблицей `pages` * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function parent() { return $this->belongsTo(Page::class); } }
Шаблон для просмотра списка views/admin/page/index.blade.php
:
@extends('layout.admin', ['title' => 'Все страницы сайта']) @section('content') <h1 class="mb-3">Все страницы сайта</h1> <a href="{{ route('admin.page.create') }}" class="btn btn-success mb-4"> Создать страницу </a> @if (count($pages)) <table class="table table-bordered"> <tr> <th>#</th> <th width="45%">Наименование</th> <th width="45%">ЧПУ (англ.)</th> <th><i class="fas fa-edit"></i></th> <th><i class="fas fa-trash-alt"></i></th> </tr> @include('admin.page.part.tree', ['level' => -1, 'parent' => 0]) </table> @endif @endsection
Вспомогательный шаблон для списка views/admin/page/part/tree.blade.php
:
@php $level++ @endphp @foreach($pages->where('parent_id', $parent) as $page) <tr> <td>{{ $page->id }}</td> <td> @if ($level) {{ str_repeat('—', $level) }} @endif <a href="{{ route('admin.page.show', ['page' => $page->id]) }}" style="font-weight:@if($level) normal @else bold @endif"> {{ $page->name }} </a> </td> <td>{{ $page->slug }}</td> <td> <a href="{{ route('admin.page.edit', ['page' => $page->id]) }}"> <i class="far fa-edit"></i> </a> </td> <td> <form action="{{ route('admin.page.destroy', ['page' => $page->id]) }}" method="post" onsubmit="return confirm('Удалить эту страницу?')"> @csrf @method('DELETE') <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="far fa-trash-alt text-danger"></i> </button> </form> </td> </tr> @if (count($pages->where('parent_id', $parent))) @include('admin.page.part.tree', ['level' => $level, 'parent' => $page->id]) @endif @endforeach
Шаблон для просмотра страницы views/admin/page/show.blade.php
:
@extends('layout.admin', ['title' => 'Просмотр страницы']) @section('content') <h1>Просмотр страницы</h1> <div class="row"> <div class="col-12"> <p><strong>Название:</strong> {{ $page->name }}</p> <p><strong>ЧПУ (англ):</strong> {{ $page->slug }}</p> <p><strong>Контент (html)</strong></p> <div class="mb-4 bg-white p-1"> @php echo nl2br(htmlspecialchars($page->content)) @endphp </div> <a href="{{ route('admin.page.edit', ['page' => $page->id]) }}" class="btn btn-success"> Редактировать страницу </a> <form method="post" class="d-inline" onsubmit="return confirm('Удалить эту страницу?')" action="{{ route('admin.page.destroy', ['page' => $page->id]) }}"> @csrf @method('DELETE') <button type="submit" class="btn btn-danger"> Удалить страницу </button> </form> </div> </div> @endsection
Шаблон для создания страницы views/admin/page/create.blade.php
:
@extends('layout.admin', ['title' => 'Создание новой страницы']) @section('content') <h1>Создание новой страницы</h1> <form method="post" action="{{ route('admin.page.store') }}"> @include('admin.page.part.form') </form> @endsection
Шаблон для редактирования страницы views/admin/page/edit.blade.php
:
@extends('layout.admin', ['title' => 'Редактирование страницы']) @section('content') <h1>Редактирование страницы</h1> <form method="post" action="{{ route('admin.page.update', ['page' => $page->id]) }}"> @method('PUT') @include('admin.page.part.form') </form> @endsection
Всмомогательный шаблон с формой views/admin/page/part/form.blade.php
:
@csrf <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Наименование" required maxlength="100" value="{{ old('name') ?? $page->name ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="slug" placeholder="ЧПУ (на англ.)" required maxlength="100" value="{{ old('slug') ?? $page->slug ?? '' }}"> </div> <div class="form-group"> @php $parent_id = old('parent_id') ?? $page->parent_id ?? 0; @endphp <select name="parent_id" class="form-control" title="Родитель"> <option value="0">Без родителя</option> @foreach($parents as $parent) <option value="{{ $parent->id }}" @if ($parent->id == $parent_id) selected @endif> {{ $parent->name }} </option> @endforeach </select> </div> <div class="form-group"> <textarea class="form-control" name="content" placeholder="Контент (html)" required rows="10">{{ old('content') ?? $page->content ?? '' }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Сохранить</button> </div>
- Магазин на Laravel 7, часть 17. Панель управления, работа с заказами, изменение статуса
- Магазин на Laravel 7, часть 16. Панель управления, CRUD-операции для товаров каталога
- Магазин на Laravel 7, часть 15. Панель управления, добавление и редактирование брендов
- Магазин на Laravel 7, часть 13. Панель управления, обрезка изображения и валидация данных
- Магазин на Laravel 7, часть 12. Панель управления, создание и редактирование категорий
- Магазин на Laravel 7, часть 24. Фильтр товаров категории по цене, новинкам и лидерам продаж
- Магазин на Laravel 7, часть 23. Главная страница сайта, новинки, лидеры продаж и распродажа
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Интернет магазин • Каталог товаров • Панель управления • Практика • Фреймворк • Шаблон сайта