re 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. ```php
  2. <?php
  3. return [
  4. 'name' => env('APP_NAME', 'Laravel'),
  5. 'env' => env('APP_ENV', 'production'),
  6. 'debug' => (bool) env('APP_DEBUG', false),
  7. 'url' => env('APP_URL', 'http://localhost'),
  8. 'timezone' => env('APP_TIMEZONE', 'UTC'),
  9. 'locale' => env('APP_LOCALE', 'en'),
  10. 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
  11. 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
  12. // ... остальные настройки по умолчанию
  13. ];
  14. ```
  15. ## Шаг 4: Создание миграций
  16. ### Migration: create_posts_table
  17. ```bash
  18. php artisan make:migration create_posts_table
  19. ```
  20. ```php
  21. <?php
  22. // database/migrations/xxxx_xx_xx_create_posts_table.php
  23. use Illuminate\Database\Migrations\Migration;
  24. use Illuminate\Database\Schema\Blueprint;
  25. use Illuminate\Support\Facades\Schema;
  26. return new class extends Migration
  27. {
  28. public function up(): void
  29. {
  30. Schema::create('posts', function (Blueprint $table) {
  31. $table->id();
  32. $table->string('title');
  33. $table->string('slug')->unique();
  34. $table->text('content');
  35. $table->enum('status', ['draft', 'published', 'scheduled'])->default('draft');
  36. $table->timestamp('published_at')->nullable();
  37. $table->timestamp('scheduled_at')->nullable();
  38. $table->timestamps();
  39. $table->softDeletes();
  40. $table->index('status');
  41. $table->index('scheduled_at');
  42. });
  43. }
  44. public function down(): void
  45. {
  46. Schema::dropIfExists('posts');
  47. }
  48. };
  49. ```
  50. ### Migration: create_comments_table
  51. ```bash
  52. php artisan make:migration create_comments_table
  53. ```
  54. ```php
  55. <?php
  56. // database/migrations/xxxx_xx_xx_create_comments_table.php
  57. use Illuminate\Database\Migrations\Migration;
  58. use Illuminate\Database\Schema\Blueprint;
  59. use Illuminate\Support\Facades\Schema;
  60. return new class extends Migration
  61. {
  62. public function up(): void
  63. {
  64. Schema::create('comments', function (Blueprint $table) {
  65. $table->id();
  66. $table->foreignId('post_id')->constrained()->onDelete('cascade');
  67. $table->string('author_name');
  68. $table->string('author_email');
  69. $table->text('content');
  70. $table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
  71. $table->timestamps();
  72. $table->index(['post_id', 'status']);
  73. });
  74. }
  75. public function down(): void
  76. {
  77. Schema::dropIfExists('comments');
  78. }
  79. };
  80. ```
  81. Запустите миграции:
  82. ```bash
  83. php artisan migrate
  84. ```
  85. ## Шаг 5: Создание моделей
  86. ### Model: Post
  87. ```bash
  88. php artisan make:model Post
  89. ```
  90. ```php
  91. <?php
  92. // app/Models/Post.php
  93. namespace App\Models;
  94. use Illuminate\Database\Eloquent\Factories\HasFactory;
  95. use Illuminate\Database\Eloquent\Model;
  96. use Illuminate\Database\Eloquent\SoftDeletes;
  97. use Illuminate\Support\Str;
  98. use App\Events\PostPublished;
  99. use App\Events\PostScheduled;
  100. class Post extends Model
  101. {
  102. use HasFactory, SoftDeletes;
  103. protected $fillable = [
  104. 'title',
  105. 'slug',
  106. 'content',
  107. 'status',
  108. 'published_at',
  109. 'scheduled_at',
  110. ];
  111. protected $casts = [
  112. 'published_at' => 'datetime',
  113. 'scheduled_at' => 'datetime',
  114. ];
  115. protected $dispatchesEvents = [
  116. 'created' => PostPublished::class,
  117. ];
  118. // Автоматическое создание slug
  119. protected static function boot()
  120. {
  121. parent::boot();
  122. static::creating(function ($post) {
  123. if (empty($post->slug)) {
  124. $post->slug = Str::slug($post->title);
  125. }
  126. });
  127. }
  128. // Связь с комментариями
  129. public function comments()
  130. {
  131. return $this->hasMany(Comment::class);
  132. }
  133. // Только опубликованные посты
  134. public function scopePublished($query)
  135. {
  136. return $query->where('status', 'published')
  137. ->whereNotNull('published_at')
  138. ->where('published_at', '<=', now());
  139. }
  140. // Посты, готовые к автопубликации
  141. public function scopeReadyForPublishing($query)
  142. {
  143. return $query->where('status', 'scheduled')
  144. ->whereNotNull('scheduled_at')
  145. ->where('scheduled_at', '<=', now());
  146. }
  147. // Публикация поста
  148. public function publish()
  149. {
  150. $this->update([
  151. 'status' => 'published',
  152. 'published_at' => now(),
  153. ]);
  154. event(new PostPublished($this));
  155. }
  156. // Снятие с публикации
  157. public function unpublish()
  158. {
  159. $this->update([
  160. 'status' => 'draft',
  161. ]);
  162. }
  163. // Планирование публикации
  164. public function schedule($dateTime)
  165. {
  166. $this->update([
  167. 'status' => 'scheduled',
  168. 'scheduled_at' => $dateTime,
  169. ]);
  170. event(new PostScheduled($this));
  171. }
  172. }
  173. ```
  174. ### Model: Comment
  175. ```bash
  176. php artisan make:model Comment
  177. ```
  178. ```php
  179. <?php
  180. // app/Models/Comment.php
  181. namespace App\Models;
  182. use Illuminate\Database\Eloquent\Factories\HasFactory;
  183. use Illuminate\Database\Eloquent\Model;
  184. use App\Events\CommentCreated;
  185. use App\Events\CommentApproved;
  186. class Comment extends Model
  187. {
  188. use HasFactory;
  189. protected $fillable = [
  190. 'post_id',
  191. 'author_name',
  192. 'author_email',
  193. 'content',
  194. 'status',
  195. ];
  196. protected $dispatchesEvents = [
  197. 'created' => CommentCreated::class,
  198. ];
  199. // Связь с постом
  200. public function post()
  201. {
  202. return $this->belongsTo(Post::class);
  203. }
  204. // Только одобренные комментарии
  205. public function scopeApproved($query)
  206. {
  207. return $query->where('status', 'approved');
  208. }
  209. // Ожидающие модерации
  210. public function scopePending($query)
  211. {
  212. return $query->where('status', 'pending');
  213. }
  214. // Одобрить комментарий
  215. public function approve()
  216. {
  217. $this->update(['status' => 'approved']);
  218. event(new CommentApproved($this));
  219. }
  220. // Отклонить комментарий
  221. public function reject()
  222. {
  223. $this->update(['status' => 'rejected']);
  224. }
  225. }
  226. ```
  227. ## Шаг 6: Создание событий (Events)
  228. ### Event: PostPublished
  229. ```bash
  230. php artisan make:event PostPublished
  231. ```
  232. ```php
  233. <?php
  234. // app/Events/PostPublished.php
  235. namespace App\Events;
  236. use App\Models\Post;
  237. use Illuminate\Broadcasting\InteractsWithSockets;
  238. use Illuminate\Foundation\Events\Dispatchable;
  239. use Illuminate\Queue\SerializesModels;
  240. class PostPublished
  241. {
  242. use Dispatchable, InteractsWithSockets, SerializesModels;
  243. public function __construct(public Post $post)
  244. {
  245. }
  246. }
  247. ```
  248. ### Event: PostScheduled
  249. ```bash
  250. php artisan make:event PostScheduled
  251. ```
  252. ```php
  253. <?php
  254. // app/Events/PostScheduled.php
  255. namespace App\Events;
  256. use App\Models\Post;
  257. use Illuminate\Broadcasting\InteractsWithSockets;
  258. use Illuminate\Foundation\Events\Dispatchable;
  259. use Illuminate\Queue\SerializesModels;
  260. class PostScheduled
  261. {
  262. use Dispatchable, InteractsWithSockets, SerializesModels;
  263. public function __construct(public Post $post)
  264. {
  265. }
  266. }
  267. ```
  268. ### Event: CommentCreated
  269. ```bash
  270. php artisan make:event CommentCreated
  271. ```
  272. ```php
  273. <?php
  274. // app/Events/CommentCreated.php
  275. namespace App\Events;
  276. use App\Models\Comment;
  277. use Illuminate\Broadcasting\InteractsWithSockets;
  278. use Illuminate\Foundation\Events\Dispatchable;
  279. use Illuminate\Queue\SerializesModels;
  280. class CommentCreated
  281. {
  282. use Dispatchable, InteractsWithSockets, SerializesModels;
  283. public function __construct(public Comment $comment)
  284. {
  285. }
  286. }
  287. ```
  288. ### Event: CommentApproved
  289. ```bash
  290. php artisan make:event CommentApproved
  291. ```
  292. ```php
  293. <?php
  294. // app/Events/CommentApproved.php
  295. namespace App\Events;
  296. use App\Models\Comment;
  297. use Illuminate\Broadcasting\InteractsWithSockets;
  298. use Illuminate\Foundation\Events\Dispatchable;
  299. use Illuminate\Queue\SerializesModels;
  300. class CommentApproved
  301. {
  302. use Dispatchable, InteractsWithSockets, SerializesModels;
  303. public function __construct(public Comment $comment)
  304. {
  305. }
  306. }
  307. ```
  308. ## Шаг 7: Создание слушателей (Listeners)
  309. ### Listener: SendPostPublishedNotification
  310. ```bash
  311. php artisan make:listener SendPostPublishedNotification --event=PostPublished
  312. ```
  313. ```php
  314. <?php
  315. // app/Listeners/SendPostPublishedNotification.php
  316. namespace App\Listeners;
  317. use App\Events\PostPublished;
  318. use Illuminate\Support\Facades\Log;
  319. class SendPostPublishedNotification
  320. {
  321. public function handle(PostPublished $event): void
  322. {
  323. Log::info('Post published: ' . $event->post->title);
  324. // Здесь можно добавить отправку уведомлений, email и т.д.
  325. }
  326. }
  327. ```
  328. ### Listener: LogPostScheduled
  329. ```bash
  330. php artisan make:listener LogPostScheduled --event=PostScheduled
  331. ```
  332. ```php
  333. <?php
  334. // app/Listeners/LogPostScheduled.php
  335. namespace App\Listeners;
  336. use App\Events\PostScheduled;
  337. use Illuminate\Support\Facades\Log;
  338. class LogPostScheduled
  339. {
  340. public function handle(PostScheduled $event): void
  341. {
  342. Log::info('Post scheduled: ' . $event->post->title .
  343. ' for ' . $event->post->scheduled_at);
  344. }
  345. }
  346. ```
  347. ### Listener: NotifyAdminAboutComment
  348. ```bash
  349. php artisan make:listener NotifyAdminAboutComment --event=CommentCreated
  350. ```
  351. ```php
  352. <?php
  353. // app/Listeners/NotifyAdminAboutComment.php
  354. namespace App\Listeners;
  355. use App\Events\CommentCreated;
  356. use Illuminate\Support\Facades\Log;
  357. class NotifyAdminAboutComment
  358. {
  359. public function handle(CommentCreated $event): void
  360. {
  361. Log::info('New comment awaiting moderation on post: ' .
  362. $event->comment->post->title);
  363. // Здесь можно отправить email администратору
  364. }
  365. }
  366. ```
  367. ### Listener: NotifyCommentAuthor
  368. ```bash
  369. php artisan make:listener NotifyCommentAuthor --event=CommentApproved
  370. ```
  371. ```php
  372. <?php
  373. // app/Listeners/NotifyCommentAuthor.php
  374. namespace App\Listeners;
  375. use App\Events\CommentApproved;
  376. use Illuminate\Support\Facades\Log;
  377. class NotifyCommentAuthor
  378. {
  379. public function handle(CommentApproved $event): void
  380. {
  381. Log::info('Comment approved and author notified: ' .
  382. $event->comment->author_email);
  383. // Здесь можно отправить email автору комментария
  384. }
  385. }
  386. ```
  387. ## Шаг 8: Регистрация событий (EventServiceProvider)
  388. ```php
  389. <?php
  390. // app/Providers/EventServiceProvider.php
  391. namespace App\Providers;
  392. use App\Events\PostPublished;
  393. use App\Events\PostScheduled;
  394. use App\Events\CommentCreated;
  395. use App\Events\CommentApproved;
  396. use App\Listeners\SendPostPublishedNotification;
  397. use App\Listeners\LogPostScheduled;
  398. use App\Listeners\NotifyAdminAboutComment;
  399. use App\Listeners\NotifyCommentAuthor;
  400. use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
  401. class EventServiceProvider extends ServiceProvider
  402. {
  403. protected $listen = [
  404. PostPublished::class => [
  405. SendPostPublishedNotification::class,
  406. ],
  407. PostScheduled::class => [
  408. LogPostScheduled::class,
  409. ],
  410. CommentCreated::class => [
  411. NotifyAdminAboutComment::class,
  412. ],
  413. CommentApproved::class => [
  414. NotifyCommentAuthor::class,
  415. ],
  416. ];
  417. public function boot(): void
  418. {
  419. //
  420. }
  421. }
  422. ```
  423. ## Шаг 9: Консольная команда для автопубликации
  424. ```bash
  425. php artisan make:command PublishScheduledPosts
  426. ```
  427. ```php
  428. <?php
  429. // app/Console/Commands/PublishScheduledPosts.php
  430. namespace App\Console\Commands;
  431. use App\Models\Post;
  432. use Illuminate\Console\Command;
  433. class PublishScheduledPosts extends Command
  434. {
  435. protected $signature = 'posts:publish-scheduled';
  436. protected $description = 'Публикация запланированных постов';
  437. public function handle()
  438. {
  439. $posts = Post::readyForPublishing()->get();
  440. if ($posts->isEmpty()) {
  441. $this->info('Нет постов для публикации');
  442. return 0;
  443. }
  444. foreach ($posts as $post) {
  445. $post->publish();
  446. $this->info("Опубликован пост: {$post->title}");
  447. }
  448. $this->info("Всего опубликовано постов: {$posts->count()}");
  449. return 0;
  450. }
  451. }
  452. ```
  453. ## Шаг 10: Настройка планировщика (Kernel.php)
  454. ```php
  455. <?php
  456. // app/Console/Kernel.php
  457. namespace App\Console;
  458. use Illuminate\Console\Scheduling\Schedule;
  459. use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
  460. class Kernel extends ConsoleKernel
  461. {
  462. protected function schedule(Schedule $schedule): void
  463. {
  464. // Проверка и публикация запланированных постов каждые 5 минут
  465. $schedule->command('posts:publish-scheduled')
  466. ->everyFiveMinutes()
  467. ->withoutOverlapping();
  468. }
  469. protected function commands(): void
  470. {
  471. $this->load(__DIR__.'/Commands');
  472. require base_path('routes/console.php');
  473. }
  474. }
  475. ```
  476. ## Шаг 11: Создание контроллеров
  477. ### PostController
  478. ```bash
  479. php artisan make:controller PostController --resource
  480. ```
  481. ```php
  482. <?php
  483. // app/Http/Controllers/PostController.php
  484. namespace App\Http\Controllers;
  485. use App\Models\Post;
  486. use Illuminate\Http\Request;
  487. class PostController extends Controller
  488. {
  489. public function index()
  490. {
  491. $posts = Post::published()->latest('published_at')->paginate(10);
  492. return view('posts.index', compact('posts'));
  493. }
  494. public function show(Post $post)
  495. {
  496. if ($post->status !== 'published') {
  497. abort(404);
  498. }
  499. $comments = $post->comments()->approved()->latest()->get();
  500. return view('posts.show', compact('post', 'comments'));
  501. }
  502. public function create()
  503. {
  504. return view('posts.create');
  505. }
  506. public function store(Request $request)
  507. {
  508. $validated = $request->validate([
  509. 'title' => 'required|max:255',
  510. 'content' => 'required',
  511. 'status' => 'required|in:draft,published,scheduled',
  512. 'scheduled_at' => 'nullable|date|after:now',
  513. ]);
  514. $post = Post::create($validated);
  515. if ($validated['status'] === 'published') {
  516. $post->publish();
  517. } elseif ($validated['status'] === 'scheduled' && $validated['scheduled_at']) {
  518. $post->schedule($validated['scheduled_at']);
  519. }
  520. return redirect()->route('posts.show', $post)
  521. ->with('success', 'Пост успешно создан');
  522. }
  523. public function edit(Post $post)
  524. {
  525. return view('posts.edit', compact('post'));
  526. }
  527. public function update(Request $request, Post $post)
  528. {
  529. $validated = $request->validate([
  530. 'title' => 'required|max:255',
  531. 'content' => 'required',
  532. 'status' => 'required|in:draft,published,scheduled',
  533. 'scheduled_at' => 'nullable|date|after:now',
  534. ]);
  535. $post->update($validated);
  536. if ($validated['status'] === 'published' && $post->status !== 'published') {
  537. $post->publish();
  538. } elseif ($validated['status'] === 'scheduled' && $validated['scheduled_at']) {
  539. $post->schedule($validated['scheduled_at']);
  540. }
  541. return redirect()->route('posts.show', $post)
  542. ->with('success', 'Пост обновлен');
  543. }
  544. public function destroy(Post $post)
  545. {
  546. $post->delete();
  547. return redirect()->route('posts.index')
  548. ->with('success', 'Пост удален');
  549. }
  550. }
  551. ```
  552. ### CommentController
  553. ```bash
  554. php artisan make:controller CommentController
  555. ```
  556. ```php
  557. <?php
  558. // app/Http/Controllers/CommentController.php
  559. namespace App\Http\Controllers;
  560. use App\Models\Post;
  561. use App\Models\Comment;
  562. use Illuminate\Http\Request;
  563. class CommentController extends Controller
  564. {
  565. public function store(Request $request, Post $post)
  566. {
  567. $validated = $request->validate([
  568. 'author_name' => 'required|max:255',
  569. 'author_email' => 'required|email',
  570. 'content' => 'required|max:1000',
  571. ]);
  572. $post->comments()->create($validated);
  573. return redirect()->route('posts.show', $post)
  574. ->with('success', 'Комментарий отправлен на модерацию');
  575. }
  576. public function moderate()
  577. {
  578. $comments = Comment::pending()->with('post')->latest()->paginate(20);
  579. return view('comments.moderate', compact('comments'));
  580. }
  581. public function approve(Comment $comment)
  582. {
  583. $comment->approve();
  584. return back()->with('success', 'Комментарий одобрен');
  585. }
  586. public function reject(Comment $comment)
  587. {
  588. $comment->reject();
  589. return back()->with('success', 'Комментарий отклонен');
  590. }
  591. }
  592. ```
  593. ## Шаг 12: Маршруты (routes/web.php)
  594. ```php
  595. <?php
  596. // routes/web.php
  597. use App\Http\Controllers\PostController;
  598. use App\Http\Controllers\CommentController;
  599. use Illuminate\Support\Facades\Route;
  600. Route::get('/', [PostController::class, 'index'])->name('home');
  601. Route::resource('posts', PostController::class);
  602. Route::post('posts/{post}/comments', [CommentController::class, 'store'])
  603. ->name('comments.store');
  604. // Административные маршруты
  605. Route::prefix('admin')->group(function () {
  606. Route::get('comments/moderate', [CommentController::class, 'moderate'])
  607. ->name('comments.moderate');
  608. Route::post('comments/{comment}/approve', [CommentController::class, 'approve'])
  609. ->name('comments.approve');
  610. Route::post('comments/{comment}/reject', [CommentController::class, 'reject'])
  611. ->name('comments.reject');
  612. });