'a', '/[èéẹẻẽêềếệểễ]/u' => 'e', '/[ìíịỉĩ]/u' => 'i', '/[òóọỏõôồốộổỗơờớợởỡ]/u' => 'o', '/[ùúụủũưừứựửữ]/u' => 'u', '/[ỳýỵỷỹ]/u' => 'y', '/[đ]/u' => 'd', ]; $text = mb_strtolower(trim($text), 'UTF-8'); $text = preg_replace(array_keys($map), array_values($map), $text); $text = preg_replace('/[^a-z0-9\s-]/', '', $text); return trim(preg_replace('/[\s-]+/', '-', $text), '-'); } $pdo = null; $dbError = null; try { $pdo = getPdoConnection(); $pdo->exec("CREATE TABLE IF NOT EXISTS `articles` ( `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `title` VARCHAR(255) NOT NULL, `slug` VARCHAR(255) NOT NULL UNIQUE, `category` VARCHAR(100) NOT NULL DEFAULT 'Laravel', `summary` VARCHAR(500) NOT NULL, `content` TEXT NOT NULL, `author` VARCHAR(120) NOT NULL DEFAULT 'Admin', `views` INT UNSIGNED NOT NULL DEFAULT 0, `published_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); $count = (int) $pdo->query("SELECT COUNT(*) FROM `articles`")->fetchColumn(); if ($count === 0) { $pdo->exec("INSERT IGNORE INTO `articles` (`id`, `title`, `slug`, `category`, `summary`, `content`, `author`, `views`, `published_at`) VALUES (1, 'Làm chủ Laravel Routing nâng cao và Route Group', 'lam-chu-laravel-routing-nang-cao', 'Routing', 'Tìm hiểu cách khai báo tham số động, biểu thức chính quy Regex, Named Routes và gom nhóm prefix admin.', 'Laravel cung cấp hệ thống định tuyến (Routing) vô cùng linh hoạt và mạnh mẽ.', 'Hoàng Vũ', 320, '2026-01-10 08:30:00'), (2, 'Xây dựng RESTful Resource Controller chuẩn mực trong Laravel', 'xay-dung-restful-resource-controller-laravel', 'Controller', 'Khám phá 7 phương thức chuẩn của Resource Controller: index, create, store, show, edit, update và destroy.', 'Chỉ với một dòng lệnh Route::resource, Laravel tự động ánh xạ 7 chuẩn RESTful HTTP Verbs.', 'Minh Tuấn', 285, '2026-01-12 09:15:00'), (3, 'Tổ chức giao diện kế thừa với Blade Layout, Partials và Stack', 'to-chuc-giao-dien-voi-blade-layout-stack', 'Blade View', 'Sử dụng @extends, @section, @yield, @include và @stack/@push để xây dựng kiến trúc giao diện mô-đun hóa.', 'Blade Template Engine cho phép tách biệt khung giao diện chính và các thành phần con.', 'Thu Hà', 410, '2026-01-15 14:20:00'), (4, 'Tái sử dụng UI hiệu quả bằng Anonymous & Class-based Blade Components', 'tai-su-dung-ui-bang-blade-components', 'Components', 'Tạo các thẻ giao diện tùy chỉnh , , gọn nhẹ, dễ bảo trì.', 'Blade Component giúp đóng gói giao diện và truyền dữ liệu qua thuộc tính.', 'Hoàng Vũ', 195, '2026-01-18 10:45:00'), (5, 'Bảo mật Form với CSRF, Method Spoofing và Route Model Binding', 'bao-mat-form-csrf-method-spoofing-model-binding', 'Security & Eloquent', 'Kết hợp @csrf, @method(''PUT''/''DELETE''), Validation và Implicit Route Model Binding trong thực tế.', 'Trong Laravel, mọi biểu mẫu POST/PUT/DELETE đều được bảo vệ khỏi tấn công CSRF.', 'Hải Đăng', 540, '2026-01-20 16:00:00')"); } } catch (Throwable $e) { $dbError = $e->getMessage(); } $mode = $_GET['mode'] ?? 'create'; // 'create' hoặc 'edit' $editId = isset($_GET['id']) ? (int) $_GET['id'] : 1; $errors = []; $flashSuccess = null; // Dữ liệu mặc định của form $formData = [ 'title' => '', 'slug' => '', 'category' => 'Laravel', 'author' => 'Hoàng Vũ', 'summary' => '', 'content' => '', ]; if ($mode === 'edit' && $pdo && $_SERVER['REQUEST_METHOD'] !== 'POST') { $stmt = $pdo->prepare("SELECT * FROM `articles` WHERE `id` = ?"); $stmt->execute([$editId]); $row = $stmt->fetch(); if ($row) { $formData = $row; } } // Xử lý submit form (POST hoặc PUT qua _method) if ($_SERVER['REQUEST_METHOD'] === 'POST' && $pdo) { $csrfToken = $_POST['_token'] ?? ''; $spoofedMethod = strtoupper($_POST['_method'] ?? 'POST'); if (!hash_equals($_SESSION['csrf_token'], $csrfToken)) { $errors['_token'] = 'CSRF Token không hợp lệ (419 Page Expired).'; } $formData['title'] = trim($_POST['title'] ?? ''); $formData['slug'] = trim($_POST['slug'] ?? ''); $formData['category'] = trim($_POST['category'] ?? ''); $formData['author'] = trim($_POST['author'] ?? ''); $formData['summary'] = trim($_POST['summary'] ?? ''); $formData['content'] = trim($_POST['content'] ?? ''); if ($formData['slug'] === '' && $formData['title'] !== '') { $formData['slug'] = makeSlug($formData['title']); } // Kiểm tra luật Validation giống ArticleController if ($formData['title'] === '') { $errors['title'] = 'Tiêu đề bài viết không được để trống.'; } elseif (mb_strlen($formData['title']) < 5) { $errors['title'] = 'Tiêu đề bài viết phải có ít nhất 5 ký tự.'; } if ($formData['slug'] === '') { $errors['slug'] = 'Đường dẫn tĩnh (Slug) không được để trống.'; } elseif (!preg_match('/^[a-z0-9-]+$/', $formData['slug'])) { $errors['slug'] = 'Slug chỉ được chứa chữ thường a-z, số 0-9 và dấu gạch ngang (-).'; } else { // Kiểm tra trùng slug if ($spoofedMethod === 'PUT') { $chk = $pdo->prepare("SELECT COUNT(*) FROM `articles` WHERE `slug` = ? AND `id` <> ?"); $chk->execute([$formData['slug'], $editId]); } else { $chk = $pdo->prepare("SELECT COUNT(*) FROM `articles` WHERE `slug` = ?"); $chk->execute([$formData['slug']]); } if ((int) $chk->fetchColumn() > 0) { $errors['slug'] = 'Slug này đã tồn tại trong CSDL, vui lòng chọn slug khác.'; } } if ($formData['category'] === '') { $errors['category'] = 'Vui lòng nhập chuyên mục bài viết.'; } if ($formData['author'] === '') { $errors['author'] = 'Tên tác giả không được để trống.'; } if ($formData['summary'] === '' || mb_strlen($formData['summary']) < 10) { $errors['summary'] = 'Tóm tắt bài viết phải có ít nhất 10 ký tự.'; } if ($formData['content'] === '' || mb_strlen($formData['content']) < 20) { $errors['content'] = 'Nội dung chi tiết phải có ít nhất 20 ký tự.'; } if (empty($errors)) { if ($spoofedMethod === 'PUT') { $upd = $pdo->prepare("UPDATE `articles` SET `title` = ?, `slug` = ?, `category` = ?, `author` = ?, `summary` = ?, `content` = ? WHERE `id` = ?"); $upd->execute([ $formData['title'], $formData['slug'], $formData['category'], $formData['author'], $formData['summary'], $formData['content'], $editId, ]); $flashSuccess = "Đã cập nhật bài viết #{$editId} «{$formData['title']}» bằng phương thức PUT (@method('PUT')) thành công!"; } else { $ins = $pdo->prepare("INSERT INTO `articles` (`title`, `slug`, `category`, `author`, `summary`, `content`, `views`, `published_at`) VALUES (?, ?, ?, ?, ?, ?, 1, NOW())"); $ins->execute([ $formData['title'], $formData['slug'], $formData['category'], $formData['author'], $formData['summary'], $formData['content'], ]); $newId = (int) $pdo->lastInsertId(); $flashSuccess = "Đã thêm bài viết mới #{$newId} «{$formData['title']}» vào bảng articles thành công!"; $formData = ['title' => '', 'slug' => '', 'category' => 'Laravel', 'author' => 'Hoàng Vũ', 'summary' => '', 'content' => '']; } } } $allArticles = $pdo ? $pdo->query("SELECT `id`, `title`, `slug`, `category`, `author` FROM `articles` ORDER BY `id` DESC")->fetchAll() : []; $createBladeCode = file_get_contents(__DIR__ . '/../resources/views/articles/create.blade.php'); $editBladeCode = file_get_contents(__DIR__ . '/../resources/views/articles/edit.blade.php'); ?> Bài 05: Form Tạo/Sửa Bài viết với @csrf, @method('PUT') & Validation - Lab 07
Dữ liệu chưa hợp lệ (`$errors->any()`):
Blade Directives đang sinh ra: @csrf @method(\'PUT\')' : ' (POST articles.store)' ?>
Chọn bài viết trong MySQL để Sửa (`edit`)
resources/views/articles/create.blade.php
resources/views/articles/edit.blade.php