```php env('APP_NAME', 'Laravel'), 'env' => env('APP_ENV', 'production'), 'debug' => (bool) env('APP_DEBUG', false), 'url' => env('APP_URL', 'http://localhost'), 'timezone' => env('APP_TIMEZONE', 'UTC'), 'locale' => env('APP_LOCALE', 'en'), 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), // ... остальные настройки по умолчанию ]; ``` ## Шаг 4: Создание миграций ### Migration: create_posts_table ```bash php artisan make:migration create_posts_table ``` ```php id(); $table->string('title'); $table->string('slug')->unique(); $table->text('content'); $table->enum('status', ['draft', 'published', 'scheduled'])->default('draft'); $table->timestamp('published_at')->nullable(); $table->timestamp('scheduled_at')->nullable(); $table->timestamps(); $table->softDeletes(); $table->index('status'); $table->index('scheduled_at'); }); } public function down(): void { Schema::dropIfExists('posts'); } }; ``` ### Migration: create_comments_table ```bash php artisan make:migration create_comments_table ``` ```php id(); $table->foreignId('post_id')->constrained()->onDelete('cascade'); $table->string('author_name'); $table->string('author_email'); $table->text('content'); $table->enum('status', ['pending', 'approved', 'rejected'])->default('pending'); $table->timestamps(); $table->index(['post_id', 'status']); }); } public function down(): void { Schema::dropIfExists('comments'); } }; ``` Запустите миграции: ```bash php artisan migrate ``` ## Шаг 5: Создание моделей ### Model: Post ```bash php artisan make:model Post ``` ```php 'datetime', 'scheduled_at' => 'datetime', ]; protected $dispatchesEvents = [ 'created' => PostPublished::class, ]; // Автоматическое создание slug protected static function boot() { parent::boot(); static::creating(function ($post) { if (empty($post->slug)) { $post->slug = Str::slug($post->title); } }); } // Связь с комментариями public function comments() { return $this->hasMany(Comment::class); } // Только опубликованные посты public function scopePublished($query) { return $query->where('status', 'published') ->whereNotNull('published_at') ->where('published_at', '<=', now()); } // Посты, готовые к автопубликации public function scopeReadyForPublishing($query) { return $query->where('status', 'scheduled') ->whereNotNull('scheduled_at') ->where('scheduled_at', '<=', now()); } // Публикация поста public function publish() { $this->update([ 'status' => 'published', 'published_at' => now(), ]); event(new PostPublished($this)); } // Снятие с публикации public function unpublish() { $this->update([ 'status' => 'draft', ]); } // Планирование публикации public function schedule($dateTime) { $this->update([ 'status' => 'scheduled', 'scheduled_at' => $dateTime, ]); event(new PostScheduled($this)); } } ``` ### Model: Comment ```bash php artisan make:model Comment ``` ```php CommentCreated::class, ]; // Связь с постом public function post() { return $this->belongsTo(Post::class); } // Только одобренные комментарии public function scopeApproved($query) { return $query->where('status', 'approved'); } // Ожидающие модерации public function scopePending($query) { return $query->where('status', 'pending'); } // Одобрить комментарий public function approve() { $this->update(['status' => 'approved']); event(new CommentApproved($this)); } // Отклонить комментарий public function reject() { $this->update(['status' => 'rejected']); } } ``` ## Шаг 6: Создание событий (Events) ### Event: PostPublished ```bash php artisan make:event PostPublished ``` ```php post->title); // Здесь можно добавить отправку уведомлений, email и т.д. } } ``` ### Listener: LogPostScheduled ```bash php artisan make:listener LogPostScheduled --event=PostScheduled ``` ```php post->title . ' for ' . $event->post->scheduled_at); } } ``` ### Listener: NotifyAdminAboutComment ```bash php artisan make:listener NotifyAdminAboutComment --event=CommentCreated ``` ```php comment->post->title); // Здесь можно отправить email администратору } } ``` ### Listener: NotifyCommentAuthor ```bash php artisan make:listener NotifyCommentAuthor --event=CommentApproved ``` ```php comment->author_email); // Здесь можно отправить email автору комментария } } ``` ## Шаг 8: Регистрация событий (EventServiceProvider) ```php [ SendPostPublishedNotification::class, ], PostScheduled::class => [ LogPostScheduled::class, ], CommentCreated::class => [ NotifyAdminAboutComment::class, ], CommentApproved::class => [ NotifyCommentAuthor::class, ], ]; public function boot(): void { // } } ``` ## Шаг 9: Консольная команда для автопубликации ```bash php artisan make:command PublishScheduledPosts ``` ```php get(); if ($posts->isEmpty()) { $this->info('Нет постов для публикации'); return 0; } foreach ($posts as $post) { $post->publish(); $this->info("Опубликован пост: {$post->title}"); } $this->info("Всего опубликовано постов: {$posts->count()}"); return 0; } } ``` ## Шаг 10: Настройка планировщика (Kernel.php) ```php command('posts:publish-scheduled') ->everyFiveMinutes() ->withoutOverlapping(); } protected function commands(): void { $this->load(__DIR__.'/Commands'); require base_path('routes/console.php'); } } ``` ## Шаг 11: Создание контроллеров ### PostController ```bash php artisan make:controller PostController --resource ``` ```php latest('published_at')->paginate(10); return view('posts.index', compact('posts')); } public function show(Post $post) { if ($post->status !== 'published') { abort(404); } $comments = $post->comments()->approved()->latest()->get(); return view('posts.show', compact('post', 'comments')); } public function create() { return view('posts.create'); } public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', 'status' => 'required|in:draft,published,scheduled', 'scheduled_at' => 'nullable|date|after:now', ]); $post = Post::create($validated); if ($validated['status'] === 'published') { $post->publish(); } elseif ($validated['status'] === 'scheduled' && $validated['scheduled_at']) { $post->schedule($validated['scheduled_at']); } return redirect()->route('posts.show', $post) ->with('success', 'Пост успешно создан'); } public function edit(Post $post) { return view('posts.edit', compact('post')); } public function update(Request $request, Post $post) { $validated = $request->validate([ 'title' => 'required|max:255', 'content' => 'required', 'status' => 'required|in:draft,published,scheduled', 'scheduled_at' => 'nullable|date|after:now', ]); $post->update($validated); if ($validated['status'] === 'published' && $post->status !== 'published') { $post->publish(); } elseif ($validated['status'] === 'scheduled' && $validated['scheduled_at']) { $post->schedule($validated['scheduled_at']); } return redirect()->route('posts.show', $post) ->with('success', 'Пост обновлен'); } public function destroy(Post $post) { $post->delete(); return redirect()->route('posts.index') ->with('success', 'Пост удален'); } } ``` ### CommentController ```bash php artisan make:controller CommentController ``` ```php validate([ 'author_name' => 'required|max:255', 'author_email' => 'required|email', 'content' => 'required|max:1000', ]); $post->comments()->create($validated); return redirect()->route('posts.show', $post) ->with('success', 'Комментарий отправлен на модерацию'); } public function moderate() { $comments = Comment::pending()->with('post')->latest()->paginate(20); return view('comments.moderate', compact('comments')); } public function approve(Comment $comment) { $comment->approve(); return back()->with('success', 'Комментарий одобрен'); } public function reject(Comment $comment) { $comment->reject(); return back()->with('success', 'Комментарий отклонен'); } } ``` ## Шаг 12: Маршруты (routes/web.php) ```php name('home'); Route::resource('posts', PostController::class); Route::post('posts/{post}/comments', [CommentController::class, 'store']) ->name('comments.store'); // Административные маршруты Route::prefix('admin')->group(function () { Route::get('comments/moderate', [CommentController::class, 'moderate']) ->name('comments.moderate'); Route::post('comments/{comment}/approve', [CommentController::class, 'approve']) ->name('comments.approve'); Route::post('comments/{comment}/reject', [CommentController::class, 'reject']) ->name('comments.reject'); });