Магазин на Laravel 7, часть 14. Панель управления, доп.проверка для родителя категории
18.10.2020
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • ИнтернетМагазин • КаталогТоваров • ПанельУправления • Практика • Фреймворк
Осталась еще одна проблема — нет проверки, что при редактировании категории в качестве родителя не будет выбана эта же категория или один из ее потомков. Здесь простыми правилами валидации не обойтись — потребуется класс, который реализует интерфейс Illuminate\Contracts\Validation\Rule
. Давайте создадим такой класс с помощью artisan-команды.
> php artisan make:rule CategoryParent
namespace App\Rules; use Illuminate\Contracts\Validation\Rule; class CategoryParent implements Rule { /** * Create a new rule instance. * * @return void */ public function __construct() { // ... } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { // ... } /** * Get the validation error message. * * @return string */ public function message() { return 'The validation error message.'; } }
Кроме того, нам потребуются два метода в модели Category
, которые позволят определить, какие категории не могут быть родителями для текущей категории.
class Category extends Model { /** * Проверяет, что переданный идентификатор id может быть родителем * этой категории; что категорию не пытаются поместить внутрь себя */ public function validParent($id) { $id = (integer)$id; // получаем идентификаторы всех потомков текущей категории $ids = $this->getAllChildren($this->id); $ids[] = $this->id; return ! in_array($id, $ids); } /** * Возвращает всех потомков категории с идентификатором $id */ public function getAllChildren($id) { // получаем прямых потомков категории с идентификатором $id $children = self::where('parent_id', $id)->with('children')->get(); $ids = []; foreach ($children as $child) { $ids[] = $child->id; // для каждого прямого потомка получаем его прямых потомков if ($child->children->count()) { $ids = array_merge($ids, $this->getAllChildren($child->id)); } } return $ids; } }
Теперь в классе CategoryCatalogRequest
, где мы задавали правила валидации, добавим еще одно правило:
namespace App\Http\Requests; use App\Rules\CategoryParent; use Illuminate\Foundation\Http\FormRequest; class CategoryCatalogRequest extends FormRequest { /* ... */ public function rules() { switch ($this->method()) { case 'POST': return [ 'parent_id' => 'required|regex:~^[0-9]+$~', 'name' => 'required|max:100', 'slug' => 'required|max:100|unique:categories,slug|regex:~^[-_a-z0-9]+$~i', 'image' => 'mimes:jpeg,jpg,png|max:5000' ]; case 'PUT': case 'PATCH': // получаем объект модели категории из маршрута: admin/category/{category} $model = $this->route('category'); $id = $model->id; return [ // задаем правило валидации, что категорию не пытаются поместить внутрь себя 'parent_id' => ['required', 'regex:~^[0-9]+$~', new CategoryParent($model)], 'name' => 'required|max:100', 'slug' => 'required|max:100|unique:categories,slug,'.$id.',id|regex:~^[-_a-z0-9]+$~i', 'image' => 'mimes:jpeg,jpg,png|max:5000' ]; } } }
При создании объекта класса CategoryParent
мы передаем в конструктор объект класса модели Category
— через него мы сможем обратиться к методу validParent()
.
namespace App\Rules; use Illuminate\Contracts\Validation\Rule; use App\Models\Category; class CategoryParent implements Rule { private $category; /** * Create a new rule instance. * * @return void */ public function __construct(Category $category) { $this->category = $category; } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { return $this->category->validParent($value); } /** * Get the validation error message. * * @return string */ public function message() { return trans('validation.custom.parent_id.invalid'); } }
В файл переводе validation.php
в директории resources/lang/ru
добавим строки перевода для сообщения об ошибке.
return [ /* ... */ 'uuid' => 'Поле :attribute должно быть корректным UUID.', 'parent_id' => 'Поле «:attribute» имеет недопустимое значение', 'custom' => [ /* ... */ 'parent_id' => [ 'required' => 'Поле «:attribute» обязательно для заполнения', 'regex' => 'Поле «:attribute» должно быть целым положительным числом', 'invalid' => 'Категорию нелья поместить внутрь самой себя', ] ], 'attributes' => [ 'name' => 'Имя, Фамилия', 'slug' => 'ЧПУ (англ)', 'email' => 'Адрес почты', 'address' => 'Адрес доставки', 'phone' => 'Номер телефона', /* ... */ 'parent_id' => 'Родитель', ], ];
Теперь все готово, можно проверять:
Заголовки страниц
Установим заголовок по умолчанию в layout-шаблоне views/layout/admin.blade.php
:
<head> <!-- ..... --> <title>{{ $title ?? 'Панель управления' }}</title> <!-- ..... --> </head>
И будем передавать значение заголовка из дочерних шаблонов:
@extends('layout.admin', ['title' => 'Все категории каталога']) @section('content') <h1>Все категории</h1> <!-- ..... --> @endsection
@extends('layout.admin', ['title' => 'Просмотр категории']) @section('content') <h1>Просмотр категории</h1> <!-- ..... --> @endsection
@extends('layout.admin', ['title' => 'Создание категории']) @section('content') <h1>Создание категории</h1> <!-- ..... --> @endsection
@extends('layout.admin', ['title' => 'Редактирование категории']) @section('content') <h1>Редактирование категории</h1> <!-- ..... --> @endsection
Создание, редактирование и удаление бренда
Нам опять нужен ресурсный контроллер, который позволит выполянить CRUD-операции над брендами.
> php artisan make:controller Admin/BrandController --resource --model=Models/Brand
namespace App\Http\Controllers\Admin; use App\Helpers\ImageSaver; use App\Http\Controllers\Controller; use App\Models\Brand; use Illuminate\Http\Request; class BrandController extends Controller { private $imageSaver; public function __construct(ImageSaver $imageSaver) { $this->imageSaver = $imageSaver; } /** * Показывает список всех брендов * * @return \Illuminate\Http\Response */ public function index() { $brands = Brand::all(); return view('admin.brand.index', compact('brands')); } /** * Показывает форму для создания бренда * * @return \Illuminate\Http\Response */ public function create() { return view('admin.brand.create'); } /** * Сохраняет новый бренд в базу данных * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $data = $request->all(); $data['image'] = $this->imageSaver->upload($request, null, 'brand'); $brand = Brand::create($data); return redirect() ->route('admin.brand.show', ['brand' => $brand->id]) ->with('success', 'Новый бренд успешно создан'); } /** * Показывает страницу бренда * * @param \App\Models\Brand $brand * @return \Illuminate\Http\Response */ public function show(Brand $brand) { return view('admin.brand.show', compact('brand')); } /** * Показывает форму для редактирования бренда * * @param \App\Models\Brand $brand * @return \Illuminate\Http\Response */ public function edit(Brand $brand) { return view('admin.brand.edit',compact('brand')); } /** * Обновляет бренд (запись в таблице БД) * * @param \Illuminate\Http\Request $request * @param \App\Models\Brand $brand * @return \Illuminate\Http\Response */ public function update(Request $request, Brand $brand) { $data = $request->all(); $data['image'] = $this->imageSaver->upload($request, $brand, 'brand'); $brand->update($data); return redirect() ->route('admin.brand.show', ['brand' => $brand->id]) ->with('success', 'Бренд был успешно отредактирован'); } /** * Удаляет бренд (запись в таблице БД) * * @param \App\Models\Brand $brand * @return \Illuminate\Http\Response */ public function destroy(Brand $brand) { if ($brand->products->count()) { return back()->withErrors('Нельзя удалить бренд, у которого есть товары'); } $this->imageSaver->remove($brand, 'brand'); $brand->delete(); return redirect() ->route('admin.brand.index') ->with('success', 'Бренд каталога успешно удален'); } }
Добавляем маршруты в файл routes/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'); });
Создаем шаблон для просмотра списка брендов views/admin/brand/index.blade.php
:
@extends('layout.admin', ['title' => 'Все бренды каталога']) @section('content') <h1>Все бренды каталога</h1> <a href="{{ route('admin.brand.create') }}" class="btn btn-success mb-4"> Создать бренд </a> <table class="table table-bordered"> <tr> <th width="30%">Наименование</th> <th width="65%">Описание</th> <th><i class="fas fa-edit"></i></th> <th><i class="fas fa-trash-alt"></i></th> </tr> @foreach($brands as $brand) <tr> <td> <a href="{{ route('admin.brand.show', ['brand' => $brand->id]) }}"> {{ $brand->name }} </a> </td> <td>{{ iconv_substr($brand->content, 0, 150) }}</td> <td> <a href="{{ route('admin.brand.edit', ['brand' => $brand->id]) }}"> <i class="far fa-edit"></i> </a> </td> <td> <form action="{{ route('admin.brand.destroy', ['brand' => $brand->id]) }}" method="post"> @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> @endforeach </table> @endsection
Создаем шаблон для просмотра отдельного бренда views/admin/brand/show.blade.php
:
@extends('layout.admin', ['title' => 'Просмотр бренда']) @section('content') <h1>Просмотр бренда</h1> <div class="row"> <div class="col-md-6"> <p><strong>Название:</strong> {{ $brand->name }}</p> <p><strong>ЧПУ (англ):</strong> {{ $brand->slug }}</p> <p><strong>Краткое описание</strong></p> @isset($brand->content) <p>{{ $brand->content }}</p> @else <p>Описание отсутствует</p> @endisset </div> <div class="col-md-6"> @php if ($brand->image) { // $url = url('storage/catalog/brand/source/' . $brand->image); $url = Storage::disk('public')->url('catalog/brand/image/' . $brand->image); } else { // $url = Storage::disk('public')->url('catalog/brand/image/' . $brand->image); $url = Storage::disk('public')->url('catalog/brand/image/default.jpg'); } @endphp <img src="{{ $url }}" alt="" class="img-fluid"> </div> </div> <a href="{{ route('admin.brand.edit', ['brand' => $brand->id]) }}" class="btn btn-success"> Редактировать бренд </a> <form method="post" class="d-inline" action="{{ route('admin.brand.destroy', ['brand' => $brand->id]) }}"> @csrf @method('DELETE') <button type="submit" class="btn btn-danger"> Удалить бренд </button> </form> @endsection
- Магазин на Laravel 7, часть 18. Панель управления, пользователи и CRUD страниц сайта
- Магазин на Laravel 7, часть 17. Панель управления, работа с заказами, изменение статуса
- Магазин на Laravel 7, часть 16. Панель управления, CRUD-операции для товаров каталога
- Магазин на Laravel 7, часть 15. Панель управления, добавление и редактирование брендов
- Магазин на Laravel 7, часть 13. Панель управления, обрезка изображения и валидация данных
- Магазин на Laravel 7, часть 12. Панель управления, создание и редактирование категорий
- Магазин на Laravel 7, часть 11. Панель управления, просмотр и удаление категорий
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Интернет магазин • Каталог товаров • Практика • Фреймворк • Валидация • Панель управления