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