Магазин на Laravel 7, часть 11. Панель управления, просмотр и удаление категорий

13.10.2020

Теги: LaravelMySQLPHPWeb-разработкаБазаДанныхИнтернетМагазинКаталогТоваровПанельУправленияПрактикаФреймворк

Контроллер CategoryController

Теперь поработаем над панелью управления магазином. И начнем с категорий каталога товаров. Нам нужен ресурсный контроллер, т.е. контроллер, который позволяет выполянить CRUD-операции над категориями каталога. Создать такой контроллер можно с помощью artisan-команды.

> php artisan make:controller Admin/CategoryController --resource
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class CategoryController extends Controller {
    public function index() {
        // ...
    }

    public function create() {
        // ...
    }

    public function store(Request $request) {
        // ...
    }

    public function show($id) {
        // ...
    }

    public function edit($id) {
        // ...
    }

    public function update(Request $request, $id) {
        // ...
    }

    public function destroy($id) {
        // ...
    }
}

Но можно поступить лучше — сразу указать модель, с которой будет работать контроллер:

> php artisan make:controller Admin/CategoryController --resource --model=Models/Category
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Http\Request;

class CategoryController extends Controller {
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index() {
        // ...
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create() {
        // ...
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request) {
        // ...
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Category  $category
     * @return \Illuminate\Http\Response
     */
    public function show(Category $category) {
        // ...
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Category  $category
     * @return \Illuminate\Http\Response
     */
    public function edit(Category $category) {
        // ...
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Category  $category
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Category $category) {
        // ...
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Category  $category
     * @return \Illuminate\Http\Response
     */
    public function destroy(Category $category) {
        // ...
    }
}

Семь маршрутов для CRUD

Теперь для каждого экшена нам нужен маршрут — всего семь маршрутов. Это можно сделать с помощью одной строки кода.

/*
Тип        URI              Действие  Имя маршрута
--------------------------------------------------
GET        /item            index     item.index
GET        /item/create     create    item.create
POST       /item            store     item.store
GET        /item/{id}       show      item.show
GET        /item/{id}/edit  edit      item.edit
PUT/PATCH  /item/{id}       update    item.update
DELETE     /item/{id}       destroy   item.destroy
*/
Route::resource('item', 'ItemController');

В нашем случае будет немного сложнее — потому что нужна группировка + защита маршртутов с помощью middleware.

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');
});

Проверить маршруты можно с помощью artisan-команды:

> php artisan route:list --name=category 
+-----------+--------------------------------+------------------------+-------------------------------------------------------+------------+
| Method    | URI                            | Name                   | Action                                                | Middleware |
+-----------+--------------------------------+------------------------+-------------------------------------------------------+------------+
| GET|HEAD  | admin/category                 | admin.category.index   | App\Http\Controllers\Admin\CategoryController@index   | .......... |
| GET|HEAD  | admin/category/{category}      | admin.category.show    | App\Http\Controllers\Admin\CategoryController@show    | .......... |
| GET|HEAD  | admin/category/create          | admin.category.create  | App\Http\Controllers\Admin\CategoryController@create  | .......... |
| POST      | admin/category                 | admin.category.store   | App\Http\Controllers\Admin\CategoryController@store   | .......... |
| GET|HEAD  | admin/category/{category}/edit | admin.category.edit    | App\Http\Controllers\Admin\CategoryController@edit    | .......... |
| PUT|PATCH | admin/category/{category}      | admin.category.update  | App\Http\Controllers\Admin\CategoryController@update  | .......... |
| DELETE    | admin/category/{category}      | admin.category.destroy | App\Http\Controllers\Admin\CategoryController@destroy | .......... |
| GET|HEAD  | catalog/category/{slug}        | catalog.category       | App\Http\Controllers\CatalogController@category       | .......... |
+-----------+--------------------------------+------------------------+-------------------------------------------------------+------------+

Реализуем метод index()

Хорошо, давайте реализуем метод index() контроллера, который будет отвечать за показ всех категорий.

class CategoryController extends Controller {
    /* ... */
    public function index() {
        $roots = Category::roots();
        return view('admin.category.index', compact('roots'));
    }
    /* ... */
}

И создадим шаблон index.blade.php в директории views/admin/category:

@extends('layout.admin')

@section('content')
    <h1>Все категории</h1>
    <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>
        @include('admin.category.part.tree', ['items' => $roots, 'level' => -1])
    </table>
@endsection

Шаблон tree.blade.php в директории views/admin/category/part подключает сам себя для рекурсивного вывода всех категорий.

@if (count($items))
    @php
        $level++;
    @endphp
    @foreach ($items as $item)
        <tr>
            <td>
                @if ($level)
                    {{ str_repeat('—', $level) }}
                @endif
                <a href="{{ route('admin.category.show', ['category' => $item->id]) }}"
                   style="font-weight:@if($level) normal @else bold @endif">
                    {{ $item->name }}
                </a>
            </td>
            <td>{{ iconv_substr($item->content, 0, 150) }}</td>
            <td>
                <a href="{{ route('admin.category.edit', ['category' => $item->id]) }}">
                    <i class="far fa-edit"></i>
                </a>
            </td>
            <td>
                <form action="{{ route('admin.category.destroy', ['category' => $item->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>
        @if ($item->children->count())
            @include('admin.category.part.tree', ['items' => $item->children, 'level' => $level])
        @endif
    @endforeach
@endif

Реализуем метод show()

Теперь реализуем метод show() контроллера, который будет отвечать за показ выбранной категории.

class CategoryController extends Controller {
    /* ... */
    public function show(Category $category) {
        return view('admin.category.show', compact('category'));
    }
    /* ... */
}

И создадим шаблон show.blade.php в директории views/admin/category:

@extends('layout.admin')

@section('content')
    <h1>Просмотр категории</h1>
    <div class="row">
        <div class="col-md-6">
            <p><strong>Название:</strong> {{ $category->name }}</p>
            <p><strong>ЧПУ (англ):</strong> {{ $category->slug }}</p>
            <p><strong>Краткое описание</strong></p>
            @isset($category->content)
                <p>{{ $category->content }}</p>
            @else
                <p>Описание отсутствует</p>
            @endisset
        </div>
        <div class="col-md-6">
            <img src="https://via.placeholder.com/600x200" alt="" class="img-fluid">
        </div>
    </div>
    @if ($category->children->count())
        <p><strong>Дочерние категории</strong></p>
        <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>
            @foreach ($category->children as $child)
                <tr>
                    <td>{{ $loop->iteration }}</td>
                    <td>
                        <a href="{{ route('admin.category.show', ['category' => $child->id]) }}">
                            {{ $child->name }}
                        </a>
                    </td>
                    <td>{{ $child->slug }}</td>
                    <td>
                        <a href="{{ route('admin.category.edit', ['category' => $child->id]) }}">
                            <i class="far fa-edit"></i>
                        </a>
                    </td>
                    <td>
                        <form action="{{ route('admin.category.destroy', ['category' => $child->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>
    @else
        <p>Нет дочерних категорий</p>
    @endif
    <form method="post"
          action="{{ route('admin.category.destroy', ['category' => $category->id]) }}">
        @csrf
        @method('DELETE')
        <button type="submit" class="btn btn-danger">
            Удалить категорию
        </button>
    </form>
@endsection

Реализуем метод destroy()

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

class CategoryController extends Controller {
    /* ... */
    public function destroy(Category $category) {
        if ($category->children->count()) {
            $errors[] = 'Нельзя удалить категорию с дочерними категориями';
        }
        if ($category->products->count()) {
            $errors[] = 'Нельзя удалить категорию, которая содержит товары';
        }
        if (!empty($errors)) {
            return back()->withErrors($errors);
        }
        $category->delete();
        return redirect()
            ->route('admin.category.index')
            ->with('success', 'Категория каталога успешно удалена');
    }
    /* ... */
}

Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Интернет магазин • Каталог товаров • Панель управления • Практика • Фреймворк

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.