Лаб. работа 5 “Создание форм для работы с сущностями”
Цель: Научиться реализовывать CRUD-операции (Create, Read, Update, Delete) через формы, валидировать данные и обрабатывать ошибки.
Задачи:
- Создать формы для добавления, редактирования и удаления постов.
- Реализовать валидацию данных на стороне сервера.
- Настроить отображение ошибок ввода.
- Использовать Tailwind CSS для стилизации форм.
Методическая часть
1. Создание маршрутов для CRUD
В файле routes/web.php
добавьте:
use App\Http\Controllers\PostController;
// CRUD для постов
Route::resource('posts', PostController::class)->except(['show']);
// Маршрут для отображения отдельного поста (если не включен в resource)
Route::get('posts/{post}', [PostController::class, 'show'])->name('posts.show');
2. Реализация методов контроллера
В app/Http/Controllers/PostController.php
:
a. Метод create
(форма создания):
public function create() {
$categories = Category::all();
$tags = Tag::all();
return view('posts.create', compact('categories', 'tags'));
}
b. Метод store
(сохранение данных):
public function store(Request $request) {
// Валидация
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required|min:50',
'category_id' => 'required|exists:categories,id',
'tags' => 'array|exists:tags,id',
'image' => 'nullable|image|max:2048',
]);
// Сохранение поста
$post = Post::create($validated + ['user_id' => auth()->id()]);
// Привязка тегов
$post->tags()->sync($request->tags);
// Загрузка изображения
if ($request->hasFile('image')) {
$path = $request->file('image')->store('posts', 'public');
$post->update(['image' => $path]);
}
return redirect()->route('posts.index')->with('success', 'Пост создан!');
}
c. Метод edit
(форма редактирования):
public function edit(Post $post) {
$categories = Category::all();
$tags = Tag::all();
return view('posts.edit', compact('post', 'categories', 'tags'));
}
d. Метод update
(обновление данных):
public function update(Request $request, Post $post) {
$validated = $request->validate([...]); // Аналогично методу store
$post->update($validated);
$post->tags()->sync($request->tags);
// Обновление изображения (логика аналогична store)
return redirect()->route('posts.index')->with('success', 'Пост обновлен!');
}
e. Метод destroy
(удаление):
public function destroy(Post $post) {
$post->delete();
return redirect()->route('posts.index')->with('success', 'Пост удален!');
}
3. Создание Blade-шаблонов
a. Форма создания/редактирования (resources/views/posts/create.blade.php
):
@extends('layouts.app')
@section('content')
<div class="max-w-4xl mx-auto py-8">
<h1 class="text-2xl font-bold mb-6">{{ isset($post) ? 'Редактирование поста' : 'Новый пост' }}</h1>
<form method="POST" action="{{ isset($post) ? route('posts.update', $post) : route('posts.store') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@isset($post) @method('PUT') @endisset
<!-- Поле "Заголовок" -->
<div>
<label class="block text-gray-700 mb-2">Заголовок *</label>
<input type="text" name="title" value="{{ old('title', $post->title ?? '') }}"
class="w-full rounded-md border-gray-300 @error('title') border-red-500 @enderror">
@error('title') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<!-- Поле "Категория" -->
<div>
<label class="block text-gray-700 mb-2">Категория *</label>
<select name="category_id" class="w-full rounded-md border-gray-300">
@foreach ($categories as $category)
<option value="{{ $category->id }}" {{ old('category_id', $post->category_id ?? '') == $category->id ? 'selected' : '' }}>
{{ $category->name }}
</option>
@endforeach
</select>
</div>
<!-- Поле "Теги" -->
<div>
<label class="block text-gray-700 mb-2">Теги</label>
<select name="tags[]" multiple class="w-full rounded-md border-gray-300" size="4">
@foreach ($tags as $tag)
<option value="{{ $tag->id }}" {{ in_array($tag->id, old('tags', $post->tags->pluck('id')->toArray() ?? [])) ? 'selected' : '' }}>
{{ $tag->name }}
</option>
@endforeach
</select>
</div>
<!-- Поле "Изображение" -->
<div>
<label class="block text-gray-700 mb-2">Изображение</label>
<input type="file" name="image" class="w-full">
@if (isset($post) && $post->image)
<img src="{{ asset('storage/' . $post->image) }}" class="mt-2 w-32">
@endif
</div>
<!-- Поле "Контент" -->
<div>
<label class="block text-gray-700 mb-2">Содержание *</label>
<textarea name="content" rows="6"
class="w-full rounded-md border-gray-300 @error('content') border-red-500 @enderror">{{ old('content', $post->content ?? '') }}</textarea>
@error('content') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<button type="submit" class="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700">
{{ isset($post) ? 'Обновить' : 'Создать' }}
</button>
</form>
</div>
@endsection
4. Валидация и обработка ошибок
Laravel автоматически перенаправляет обратно с ошибками при неудачной валидации.
Ошибки доступны через глобальную переменную
$errors
в Blade:
Индивидуальные задания
1. Валидация уникальности заголовка
Добавьте правило валидации, чтобы заголовок поста был уникальным:
2. Кастомное сообщение об ошибке
Создайте файл lang/ru/validation.php
и добавьте:
return [
'min' => [
'string' => 'Поле :attribute должно быть не короче :min символов.',
],
'required' => 'Поле :attribute обязательно для заполнения.',
];
В контроллере укажите локаль:
3. Preview контента
Добавьте кнопку “Предпросмотр” с динамическим отображением контента без перезагрузки страницы (используйте JavaScript):
<button type="button" onclick="document.getElementById('preview').innerHTML = document.querySelector('textarea[name=content]').value"
class="bg-gray-200 px-4 py-2 rounded-md">
Предпросмотр
</button>
<div id="preview" class="mt-4 p-4 bg-gray-50 rounded-md"></div>
4. Удаление изображения
Добавьте чекбокс “Удалить изображение” в форму редактирования:
@if ($post->image)
<div class="mt-2">
<label class="flex items-center">
<input type="checkbox" name="remove_image" class="mr-2">
<span class="text-red-600">Удалить изображение</span>
</label>
</div>
@endif
В контроллере:
if ($request->has('remove_image')) {
Storage::delete('public/' . $post->image);
$post->update(['image' => null]);
}
Примечания
CSRF-защита: Все формы должны включать
@csrf
.
Хранение изображений: Используйте
storage/app/public
, создав симлинк: