Привет, Хабр. В этой статье я хочу поделиться своим опытом использования ресурсных контроллеров в CRUD приложении на фреймворке Laravel - простенькой CRM. Итак начнём.
Проект опубликован как свободное ПО.
Задача
Создать веб-приложение для учёта бизнес-клиентов: карточки организаций и их представителей, заявки, события, договора по принципу CRUD.
Ингредиенты
Будем использовать свободное программное обеспечение. В качестве PHP - фреймворка будем использовать Laravel и Bootstrap в качестве его компонента для построения страниц HTML. И поскольку это обычное CRUD приложение, то будем использовать ресурсные контроллеры для базовых действий: создание (create), чтение (read), модификация (update), удаление (delete).
Приступаем
Создаём проект Laravel, используя Composer:
composer create-project laravel/laravel crm.example.com cd crm.example.com
Подключаем Bootstrap, делаем форму входа для пользователей и собираем через npm:
composer require laravel/ui php artisan ui bootstrap php artisan ui bootstrap --auth npm install npm run dev
Создаём модель для бизнес-клиентов, а также заодно к ней миграцию, фабрику, наполнитель, политику, контроллер и запрос формы:
php artisan make:model Client --all
Остальные модели также. Генерируем миграции для сессий и очередей:
php artisan session:table php artisan queue:table
Создайте по необходимости в папке database/seeder файлы для наполнения базы данных первичными данными, например для создания пользователя-админа:
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; class UserSeeder extends Seeder { public function run() { DB::table('users')->insert([ [ 'name' => 'admin', 'email' => 'admin@example.com', 'access_level' => 2, 'password' => Hash::make('твой_пароль') ] ]); } }
Настраиваем миграции. Таблица для клиентов будет выглядеть примерно так:
<?php // ... class CreateClientsTable extends Migration { public function up() { Schema::create('clients', function (Blueprint $table) { $table->id(); $table->string('fulltitle', 512)->nullable(); $table->string('title', 128)->nullable(); /* ... тут много полей для всяких реквизитов ... */ $table->string('tel', 10)->nullable(); // Телефон $table->string('comment', 255)->nullable(); // Комментарий $table->foreignId('creater_id') // какой пользователь создал ->nullable() ->constrained('users') ->cascadeOnUpdate() ->nullOnDelete(); $table->foreignId('updater_id') // какой последний пользователь внёс изменения ->nullable() ->constrained('users') ->cascadeOnUpdate() ->nullOnDelete(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('clients'); } }
После запускаем миграции с наполнением БД:
php artisan migrate:fresh --seed
:fresh - удалит существующие таблицы в БД.
Итак, в папке app/Http/Controllers будет нас ждать ресурсный контроллер ClientController. Там будут объявлены методы: index, create, store, show, edit, update, destroy.
Метод index должен отображать список клиентов. Напишем так, чтобы он выводил сначала новые записи, разбивая на страницы по 20 записей, используя для вывода Blade шаблон 'client-index':
<?php // ... public function index() { return view('client-index', ['clients' => Client::orderByDesc('id')->paginate(20)]); }
Метод create должен вывести форму для заполнения данных о клиенте и кнопку для добавления его в БД. Мы используем Blade шаблон 'client-edit' и для добавления и для редактирования данных, чтобы шаблон понимал, какое именно из этих действий выполняется, мы будем передавать ему переменную edit , где значение 0 - для добавления нового, а 1 - для изменения существующего. Переменную client мы просто заполняем начальными данными из модели Client
<?php // ... public function create() { return view('client-edit', ['edit' => 0, 'client' => new Client()]); }
Метод edit похож на метод create. Только здесь edit = 1, а переменная client заполняется моделью существующего клиента, переданного через маршрут:
<?php // ... public function edit(Client $client) { return view('client-edit', ['edit' => 1, 'client' => $client]); }
Метод show отображает карточку клиента:
<?php // ... public function show(Client $client) { return view('client-show', ['client' => $client]); }
Метод store добавляет нового клиента в БД, а метод update обновляет данные у существующего в БД:
<?php // ... public function store(StoreClientRequest $request) { $validated = $request->validated(); $client = Client::create($validated); return redirect()->route('clients.show', $client); } public function update(UpdateClientRequest $request, Client $client) { $validated = $request->validated(); $client->fill($validated); $client->updater_id = Auth::id(); $client->save(); return redirect()->route('clients.show', $client); }
Метод destroy удаляет запись из БД:
<?php // ... public function destroy(Client $client) { if (Auth::user()->access_level == 2) { $client->delete(); return redirect()->route('clients.index'); } else { return null; } }
Теперь рассмотрим, что такое StoreClientRequest и UpdateClientRequest. Дело в том, что перед внесением записей в БД, мы должны проверить корректность данных и авторизовать действие пользователя, то есть пройти валидацию запроса. Они называются запросами формы и лежат в папке app/Http/Requests. Так будет примерно выглядеть StoreClientRequest, который проверяет данные нового клиента перед добавлением:
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreClientRequest extends FormRequest { */ public function authorize() { return ($this->user()->access_level > 0); // имеет ли право пользоваель на это действие } public function rules() { return [ 'fulltitle' => ['nullable', 'string', 'max:512'], 'title' => ['nullable', 'string', 'max:128'], /* ... тут проверяется много полей для всяких реквизитов ... */ 'tel' => ['nullable', 'string', 'max:10'], 'comment' => ['nullable', 'string', 'max:255'] ]; } }
UpdateClientRequest будет выглядеть примерно также.
Теперь настроим маршруты в routes/web.php:
<?php // ... // объявление маршрутов на входы пользователей, отключив маршруты регистрации, сброса пароля и верификации Auth::routes(['register' => false, 'reset' => false,'verify' => false]); // ставим посредник auth, чтобы только авторизованные пользователи могли ходить по этим маршрутам Route::group(['middleware' => 'auth'], function() { Route::resources([ 'clients' => ClientController::class, 'clients.representatives' => ClientRepresentativeController::class, /* тут ещё куча строк */ ]); });
Маршруты для методов ресурсного контроллера создаются автоматически Laravel, не надо для каждого метода прописывать отдельный маршрут. Для объявления маршрутов ресурсного контроллера мы используем Route::resources , передавая массив ресурсных контроллеров. Так для клиентов Laravel создаст следующие маршруты:
clients.index
clients.show
clients.create
clients.edit
clients.store
clients.update
clients.destroy
Теперь о 'clients.representatives' => ClientRepresentativeController::class , это объявление маршрутов к методам контроллера вложенных ресурсов. В нашей CRM ведётся учёт представителей бизнес-клиентов. Модель представителей является дочерней для модели клиентов. И чтобы мы могли увидеть, например всех представителей для конкретного клиента по адресу вроде https://crm.example.com/clients/123/representatives, мы создадим контроллер вложенных ресурсов:
<?php namespace App\Http\Controllers; use App\Http\Requests\StoreRepresentativeRequest; use App\Models\Representative; use App\Models\Client; class ClientRepresentativeController extends Controller { public function index(Client $client) { return view('representative-index', ['client' => $client, 'representatives' => Representative::where(['client_id' => $client->id])->orderByDesc('id')->paginate(20)]); } public function create(Client $client) { return view('representative-edit', ['edit' => 0, 'representative' => new Representative(['client_id' => $client->id])]); } public function store(StoreRepresentativeRequest $request, Client $client) { $validated = $request->validated(); $representative = new Representative(['client_id' => $client->id]); $representative->fill($validated); $representative->save(); return redirect()->route('representatives.show', $representative); } }
Метод index выведет список представителей для конкр��тного клиента. Метод create выведет форму для создания представителя, а также укажет для какого клиента он создаётся. Метод store сохранит запись о новом представителе с указанием id клиента.
А для остальных методов нам не нужен контроллер вложенных ресурсов, поскольку каждая существующая запись о представителях имеет уникальный идентификатор, и с ними мы можем взаимодействовать через обычный ресурсный контроллер RepresentativeController. А Laravel нам также создаст маршруты для методов контроллера вложенных ресурсов:
clients.representatives.index
clients.representatives.create
clients.representatives.store
Полностью посмотреть код, в том числе шаблоны Blade вы можете по ссылке.
