query('category'); $query = Article::query()->orderByDesc('published_at'); if (!empty($category)) { $query->where('category', $category); } $articles = $query->paginate(10); $breadcrumbs = [ ['label' => 'Trang chủ', 'url' => route('home')], ['label' => 'Danh sách bài viết', 'url' => null], ]; return view('articles.index', compact('articles', 'category', 'breadcrumbs')); } /** * 2. GET /articles/create (articles.create) * Hiển thị biểu mẫu tạo mới bài viết. */ public function create() { $breadcrumbs = [ ['label' => 'Trang chủ', 'url' => route('home')], ['label' => 'Bài viết', 'url' => route('articles.index')], ['label' => 'Tạo bài viết mới', 'url' => null], ]; return view('articles.create', compact('breadcrumbs')); } /** * 3. POST /articles (articles.store) * Kiểm tra dữ liệu hợp lệ (Validation) và lưu bài viết mới vào CSDL. */ public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|string|min:5|max:255', 'slug' => 'nullable|string|max:255|regex:/^[a-z0-9-]+$/|unique:articles,slug', 'category' => 'required|string|max:100', 'summary' => 'required|string|min:10|max:500', 'content' => 'required|string|min:20', 'author' => 'required|string|max:120', ], [ 'title.required' => 'Tiêu đề bài viết không được để trống.', 'title.min' => 'Tiêu đề bài viết phải có ít nhất :min ký tự.', 'slug.regex' => 'Slug chỉ được chứa chữ thường a-z, số 0-9 và dấu gạch ngang (-).', 'slug.unique' => 'Slug này đã tồn tại, vui lòng chọn slug khác.', 'category.required' => 'Vui lòng chọn chuyên mục bài viết.', 'summary.required' => 'Tóm tắt bài viết không được để trống.', 'summary.min' => 'Tóm tắt phải có ít nhất :min ký tự.', 'content.required' => 'Nội dung chi tiết không được để trống.', 'content.min' => 'Nội dung chi tiết phải có ít nhất :min ký tự.', 'author.required' => 'Tên tác giả không được để trống.', ]); if (empty($validated['slug'])) { $validated['slug'] = Str::slug($validated['title']); } $validated['published_at'] = now(); $article = Article::create($validated); return redirect() ->route('articles.show', $article) ->with('success', "Đã tạo bài viết «{$article->title}» thành công!"); } /** * 4. GET /articles/{article} (articles.show) * Hiển thị chi tiết một bài viết sử dụng Implicit Route Model Binding. */ public function show(Article $article) { $article->increment('views'); $breadcrumbs = [ ['label' => 'Trang chủ', 'url' => route('home')], ['label' => 'Bài viết', 'url' => route('articles.index')], ['label' => $article->title, 'url' => null], ]; return view('articles.show', compact('article', 'breadcrumbs')); } /** * 5. GET /articles/{article}/edit (articles.edit) * Hiển thị biểu mẫu chỉnh sửa bài viết với Route Model Binding. */ public function edit(Article $article) { $breadcrumbs = [ ['label' => 'Trang chủ', 'url' => route('home')], ['label' => 'Bài viết', 'url' => route('articles.index')], ['label' => $article->title, 'url' => route('articles.show', $article)], ['label' => 'Chỉnh sửa', 'url' => null], ]; return view('articles.edit', compact('article', 'breadcrumbs')); } /** * 6. PUT/PATCH /articles/{article} (articles.update) * Cập nhật bài viết hiện có sau khi kiểm tra hợp lệ. */ public function update(Request $request, Article $article) { $validated = $request->validate([ 'title' => 'required|string|min:5|max:255', 'slug' => 'required|string|max:255|regex:/^[a-z0-9-]+$/|unique:articles,slug,' . $article->id, 'category' => 'required|string|max:100', 'summary' => 'required|string|min:10|max:500', 'content' => 'required|string|min:20', 'author' => 'required|string|max:120', ], [ 'title.required' => 'Tiêu đề bài viết không được để trống.', 'title.min' => 'Tiêu đề bài viết phải có ít nhất :min ký tự.', 'slug.required' => 'Slug không được để trống.', 'slug.regex' => 'Slug chỉ được chứa chữ thường a-z, số 0-9 và dấu gạch ngang (-).', 'slug.unique' => 'Slug này đã được sử dụng ở bài viết khác.', 'category.required' => 'Vui lòng chọn chuyên mục bài viết.', 'summary.required' => 'Tóm tắt bài viết không được để trống.', 'content.required' => 'Nội dung chi tiết không được để trống.', 'author.required' => 'Tên tác giả không được để trống.', ]); $article->update($validated); return redirect() ->route('articles.show', $article) ->with('success', "Đã cập nhật bài viết «{$article->title}» thành công!"); } /** * 7. DELETE /articles/{article} (articles.destroy) * Xóa bài viết khỏi CSDL với Method Spoofing (@method('DELETE')). */ public function destroy(Article $article) { $title = $article->title; $article->delete(); return redirect() ->route('articles.index') ->with('success', "Đã xóa bài viết «{$title}» khỏi hệ thống."); } }