Урок 20: Итоговый проект, блог на Laravel. CRUD, комментарии, поиск, деплой

В 20-му уроку мы подошли к завершающему этапу нашего курса по Laravel. Сегодня мы создадим полноценный блог с нуля, используя все знания, которые вы получили за предыдущие 19 уроков. В этом уроке мы реализуем CRUD для статей, систему комментариев с возможностью ответов, поиск через Laravel Scout и задеплоим проект в интернет.

Реализация CRUD для статей

CRUD (Create, Read, Update, Delete) основа большинства веб-приложений. Начнем с создания модели, миграции и контроллера для статей.

1. Создание модели и миграции

Откройте терминал и выполните:

bash
php artisan make:model Article -mcr

Эта команда создаст модель Article, контроллер ArticleController и миграцию.

Отредактируем миграцию create_articles_table:

php
public function up() {
    Schema::create('articles', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->foreignId('user_id')->constrained();
        $table->timestamps();
    });
}

Выполните миграцию:

bash
php artisan migrate

2. Настройка отношений в модели

В модели Article добавим связь с пользователем:

php
class Article extends Model {
    protected $fillable = ['title', 'content', 'user_id'];

    public function user() {
        return $this->belongsTo(User::class);
    }
}

3. Создание маршрутов

Добавим в routes/web.php:

php
Route::resource('articles', ArticleController::class)->middleware('auth');

4. Реализация методов контроллера

В ArticleController реализуем основные методы:

Создание статьи (create, store):

php
public function create() {
    return view('articles.create');
}

public function store(Request $request) {
    $validated = $request->validate([
        'title' => 'required|max:255',
        'content' => 'required'
    ]);

    auth()->user()->articles()->create($validated);
    return redirect()->route('articles.index');
}

Отображение статей (index, show):

php
public function index() {
    $articles = Article::latest()->paginate(10);
    return view('articles.index', compact('articles'));
}

public function show(Article $article) {
    return view('articles.show', compact('article'));
}

Редактирование (edit, update) и удаление (destroy):

php
public function edit(Article $article) {
    $this->authorize('update', $article);
    return view('articles.edit', compact('article'));
}

public function update(Request $request, Article $article) {
    $this->authorize('update', $article);
    $validated = $request->validate([...]); // аналогично store
    $article->update($validated);
    return redirect()->route('articles.show', $article);
}

public function destroy(Article $article) {
    $this->authorize('delete', $article);
    $article->delete();
    return redirect()->route('articles.index');
}

5. Создание Blade-шаблонов

Пример для resources/views/articles/create.blade.php:

html
<form action="{{ route('articles.store') }}" method="POST">
    @csrf
    <input type="text" name="title" placeholder="Заголовок">
    <textarea name="content" placeholder="Текст статьи"></textarea>
    <button type="submit">Опубликовать</button>
</form>

Практическая задача: добавьте валидацию для поля content (минимум 100 символов) и кастомное сообщение об ошибке.

Система комментариев с деревом ответов

Реализуем вложенные комментарии с использованием отношения parent_id.

1. Модель и миграция для комментариев

Создайте модель и миграцию:

bash
php artisan make:model Comment -m

В миграции create_comments_table:

php
public function up() {
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->text('body');
        $table->foreignId('user_id')->constrained();
        $table->foreignId('article_id')->constrained();
        $table->foreignId('parent_id')->nullable()->constrained('comments');
        $table->timestamps();
    });
}

В модели Comment добавим отношения:

php
class Comment extends Model {
    public function article() {
        return $this->belongsTo(Article::class);
    }

    public function user() {
        return $this->belongsTo(User::class);
    }

    public function replies() {
        return $this->hasMany(Comment::class, 'parent_id');
    }
}

2. Добавление комментариев

Создадим контроллер CommentController:

php
public function store(Request $request, Article $article) {
    $request->validate(['body' => 'required|min:3']);

    $comment = new Comment([
        'body' => $request->body,
        'user_id' => auth()->id(),
        'parent_id' => $request->parent_id
    ]);

    $article->comments()->save($comment);
    return back();
}

3. Отображение вложенных комментариев

В шаблоне articles/show.blade.php:

html
@foreach ($article->comments->whereNull('parent_id') as $comment)
    @include('comments._item', ['comment' => $comment])
@endforeach

Файл resources/views/comments/_item.blade.php:

html
<div class="comment">
    <p>{{ $comment->body }}</p>
    <form action="{{ route('comments.store', $article) }}" method="POST">
        @csrf
        <input type="hidden" name="parent_id" value="{{ $comment->id }}">
        <textarea name="body" placeholder="Ответить..."></textarea>
        <button type="submit">Ответить</button>
    </form>
    
    @foreach ($comment->replies as $reply)
        @include('comments._item', ['comment' => $reply])
    @endforeach
</div>

Практическая задача: ограничьте глубину вложенности комментариев до 3 уровней. Подсказка: используйте рекурсивный счетчик в Blade.

Поиск по статьям через Scout

Laravel Scout предоставляет простой способ реализации полнотекстового поиска.

1. Установка Scout и драйвера

Установите пакеты:

bash
composer require laravel/scout
composer require laravel/scout-database-driver

В .env добавьте:

SCOUT_DRIVER=database

2. Настройка модели Article

В модели Article подключите трейт Searchable:

php
use Laravel\Scout\Searchable;

class Article extends Model {
    use Searchable;
    
    public function toSearchableArray() {
        return [
            'title' => $this->title,
            'content' => $this->content
        ];
    }
}

3. Реализация поиска

Добавьте маршрут:

php
Route::get('/search', [ArticleController::class, 'search']);

В контроллере:

php
public function search(Request $request) {
    $query = $request->input('q');
    $articles = Article::search($query)->paginate(10);
    return view('articles.search', compact('articles', 'query'));
}

В шаблоне search.blade.php:

html
<form action="{{ route('articles.search') }}">
    <input type="text" name="q" value="{{ $query }}">
    <button>Найти</button>
</form>

@foreach ($articles as $article)
    <!-- Вывод результатов -->
@endforeach

Практическая задача: добавьте поиск по комментариям. Подсказка: создайте отдельный индекс для модели Comment.

Деплой проекта

Развернем приложение на shared-хостинге. Для примера используем Hostinger.

1. Подготовка к деплою

  • Убедитесь, что APP_ENV=production в .env.
  • Выполните php artisan config:cache и php artisan route:cache.

2. Перенос файлов на сервер

Скопируйте файлы через FTP или Git. Если используете Git:

bash
git remote add production username@server:/path/to/project
git push production master

3. Настройка базы данных

Создайте БД через панель управления хостингом и обновите настройки в .env:

DB_CONNECTION=mysql
DB_HOST=...
DB_PORT=...
DB_DATABASE=...
DB_USERNAME=...
DB_PASSWORD=...

4. Запуск миграций

На сервере выполните:

bash
php artisan migrate --force

Практическая задача: настройте автоматический деплой через GitHub Actions. Подсказка: используйте секреты для хранения учетных данных.

Подведение итогов

Поздравляю! Вы только что создали полноценный блог на Laravel. Давайте вспомним, что мы покрыли:

  1. CRUD для статей с авторизацией и политиками.
  2. Вложенные комментарии с использованием рекурсивных отношений.
  3. Полнотекстовый поиск через Laravel Scout.
  4. Деплой проекта на реальный сервер.

Этот проект, отличное портфолио для старта вашей карьеры. Не останавливайтесь на достигнутом, попробуйте добавить теги, лайки, подписки на статьи.

Полный курс с уроками по Laravel для начинающих по ссылке: https://max-gabov.ru/laravel-dlya-nachinaushih