Xây Dựng Ứng Dụng CMS với CakePHP 4

Cấu Hình Môi Trường Phát Triển

Thiết lập môi trường PHP tối thiểu bao gồm Composer và máy chủ cơ sở dữ liệu. Đảm bảo phiên bản PHP từ 8.1 trở lên để tương thích với CakePHP 4.

Khởi Tạo Dự Án Mẫu

# Tạo ứng dụng blog mới
$ composer create-project --prefer-dist cakephp/app:4.* blog_app
$ cd blog_app
# Chạy máy chủ phát triển
$ bin/cake server -p 8000
# Truy cập qua http://localhost:8000

Cấu Trúc Cơ Sở Dữ Liệu

CREATE DATABASE blog_app;
USE blog_app;

CREATE TABLE authors (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    author_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    url_key VARCHAR(191) UNIQUE NOT NULL,
    content TEXT,
    is_published BOOLEAN DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (author_id) REFERENCES authors(id)
) CHARSET=utf8mb4;

CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(191) UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE post_categories (
    post_id INT NOT NULL,
    category_id INT NOT NULL,
    PRIMARY KEY (post_id, category_id),
    FOREIGN KEY (post_id) REFERENCES posts(id),
    FOREIGN KEY (category_id) REFERENCES categories(id)
);

INSERT INTO authors (email, password_hash, created_at) 
VALUES ('admin@blog.com', '$2y$10$secret_hash', NOW());

Thiết Lập Kết Nối Cơ Sở Dữ Liệu

Chỉnh sửa file config/app_local.php với thông số sau:

'Datasources' => [
    'default' => [
        'host' => '127.0.0.1',
        'database' => 'blog_app',
        'username' => 'root',
        'password' => '',
        'encoding' => 'utf8mb4',
        'timezone' => 'Asia/Ho_Chi_Minh'
    ]
]

Triển Khai Lớp Model

<?php
// src/Model/Table/PostsTable.php
namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Utility\Text;
use Cake\Validation\Validator;

class PostsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp', [
            'events' => ['Model.beforeSave' => ['updated_at' => 'always']]
        ]);
    }

    public function beforeSave($event, $entity)
    {
        if ($entity->isDirty('title') && !$entity->url_key) {
            $key = Text::slug($entity->title);
            $entity->url_key = substr($key, 0, 191);
        }
    }

    public function validationPost(Validator $validator): Validator
    {
        return $validator
            ->requirePresence('title')
            ->minLength('title', 15)
            ->maxLength('title', 255)
            ->requirePresence('content')
            ->minLength('content', 50);
    }
}
<?php
// src/Model/Entity/Post.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Post extends Entity
{
    protected $_accessible = [
        'title' => true,
        'content' => true,
        'is_published' => true,
        'url_key' => false
    ];
}

Cài Đặt Controller

<?php
// src/Controller/PostsController.php
namespace App\Controller;

use App\Controller\AppController;

class PostsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Paginator');
        $this->loadComponent('Flash');
    }

    public function index()
    {
        $posts = $this->paginate($this->Posts->find('published'));
        $this->set(compact('posts'));
    }

    public function detail($key)
    {
        $post = $this->Posts->findByUrlKey($key)->contain(['Authors'])->firstOrFail();
        $this->set(compact('post'));
    }

    public function create()
    {
        $post = $this->Posts->newEmptyEntity();
        if ($this->request->is('post')) {
            $post = $this->Posts->patchEntity($post, $this->request->getData());
            $post->author_id = 1; // Sẽ thay bằng xác thực thực tế
            
            if ($this->Posts->save($post)) {
                $this->Flash->success('Đăng bài viết thành công');
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error('Không thể lưu bài viết');
        }
        $this->set(compact('post'));
    }
}

Giao Diện Người Dùng

<!-- templates/Posts/create.php -->
<div class="form-container">
    <h2>Viết Bài Mới</h2>
    <?= $this->Form->create($post) ?>
        <?= $this->Form->control('author_id', ['type' => 'hidden']) ?>
        <?= $this->Form->control('title', ['label' => 'Tiêu đề']) ?>
        <?= $this->Form->control('content', [
            'label' => 'Nội dung',
            'rows' => 10
        ]) ?>
        <?= $this->Form->button('Đăng bài', ['class' => 'submit-btn']) ?>
    <?= $this->Form->end() ?>
</div>
<!-- templates/Posts/index.php -->
<h2>Danh Sách Bài Viết</h2>
<?= $this->Html->link('Thêm mới', ['action' => 'create'], ['class' => 'add-button']) ?>

<table class="data-table">
    <thead>
        <tr>
            <th>Tiêu đề</th>
            <th>Ngày đăng</th>
            <th>Thao tác</th>
        </tr>
    </thead>
    <tbody>
    <?php foreach ($posts as $post): ?>
        <tr>
            <td><?= $this->Html->link($post->title, ['action' => 'detail', $post->url_key]) ?></td>
            <td><?= $post->created_at->format('d/m/Y') ?></td>
            <td>
                <?= $this->Html->link('Sửa', ['action' => 'edit', $post->url_key]) ?>
                <?= $this->Form->postLink('Xóa', [
                    'action' => 'delete', $post->url_key
                ], [
                    'confirm' => 'Xác nhận xóa?',
                    'class' => 'delete-link'
                ]) ?>
            </td>
        </tr>
    <?php endforeach; ?>
    </tbody>
</table>

Thẻ: cakephp4 php-mvc database-migration form-validation restful-controllers

Đăng vào ngày 19 tháng 6 lúc 08:11