0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 8: Quản lý Vai trò (Roles) & Phân quyền cơ bản

Chào các bạn, mình đã trở lại!

Khi ứng dụng lớn dần, việc "ai cũng có quyền như ai" là một thảm họa bảo mật. Bạn không muốn một người dùng bình thường lại có quyền xóa bài viết của người khác, đúng không? Đó là lý do chúng ta cần Roles.

Trong bài này, chúng ta sẽ xây dựng các Endpoint chuẩn RESTful để quản lý danh sách vai trò và cải tiến câu lệnh SQL để lấy thông tin người dùng kèm theo tên vai trò của họ.

1. Chuẩn bị Cơ sở dữ liệu

Hãy đảm bảo bạn đã có bảng Roles và cột role_id trong bảng Users.

Bảng Roles: id, name, created_at.

Bảng Users: Thêm cột role_id (Khóa ngoại liên kết tới Roles.id).

2. Tầng Model: Quản lý Roles (Role.php)

Chúng ta tạo một Model mới để thực hiện các thao tác CRUD (Create, Read, Update, Delete) trên bảng vai trò.

File: app/Models/Role.php

<?php
namespace App\Models;

use App\Core\Database;

class Role {
    protected $db;

    public function __construct() {
        $this->db = Database::getInstance();
    }

    public function getAll() {
        $stmt = $this->db->query("SELECT * FROM Roles ORDER BY id DESC");
        return $stmt->fetchAll();
    }

    public function findById($id) {
        $stmt = $this->db->prepare("SELECT * FROM Roles WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch();
    }

    public function create($name) {
        $stmt = $this->db->prepare("INSERT INTO Roles (name, created_at) VALUES (?, NOW())");
        $stmt->execute([$name]);
        return $this->db->lastInsertId();
    }

    public function update($id, $name) {
        $stmt = $this->db->prepare("UPDATE Roles SET name = ? WHERE id = ?");
        return $stmt->execute([$name, $id]);
    }

    public function delete($id) {
        $stmt = $this->db->prepare("DELETE FROM Roles WHERE id = ?");
        return $stmt->execute([$id]);
    }
}

3. Tầng Controller: Điều phối danh sách vai trò

RoleController sẽ tiếp nhận các yêu cầu từ Client và gọi Model tương ứng.

File: app/Controllers/RoleController.php

<?php
namespace App\Controllers;

use App\Core\Response;
use App\Models\Role;

class RoleController
{
    public function index() {
        $roles = (new Role())->getAll();
        Response::json(['roles' => $roles]);
    }

    public function create() {
        $data = json_decode(file_get_contents("php://input"), true);
        if (empty($data['name'])) {
            Response::json(['error' => 'Tên vai trò là bắt buộc'], 422);
        }

        $roleId = (new Role())->create($data['name']);
        Response::json(['message' => 'Đã tạo vai trò mới', 'role_id' => $roleId], 201);
    }

    public function update($id) {
        $data = json_decode(file_get_contents("php://input"), true);
        if (empty($data['name'])) {
            Response::json(['error' => 'Tên vai trò không được để trống'], 422);
        }

        (new Role())->update($id, $data['name']);
        Response::json(['message' => 'Cập nhật vai trò thành công']);
    }

    public function delete($id) {
        (new Role())->delete($id);
        Response::json(['message' => 'Đã xóa vai trò']);
    }
}

4. Định tuyến nâng cao với Regex (index.php)

Vì các Endpoint như PUT /api/roles/{id} có tham số động trên URL, chúng ta sẽ sử dụng preg_match để "bắt" các ID này một cách chuyên nghiệp.

File: public/index.php

<?php
// ... Autoload & Imports ...
use App\Controllers\RoleController;

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$roleController = new RoleController();

// 1. GET danh sách roles
if ($uri === '/api/roles' && $method === 'GET') {
    $roleController->index();

// 2. POST tạo mới role
} elseif ($uri === '/api/roles' && $method === 'POST') {
    $roleController->create();

// 3. PUT cập nhật role (Dùng Regex để bắt ID)
} elseif (preg_match('#^/api/roles/(\d+)$#', $uri, $matches) && $method === 'PUT') {
    $roleId = $matches[1];
    $roleController->update($roleId);

// 4. DELETE xóa role
} elseif (preg_match('#^/api/roles/(\d+)$#', $uri, $matches) && $method === 'DELETE') {
    $roleId = $matches[1];
    $roleController->delete($roleId);
}

5. Bonus: Lấy thông tin User kèm Role Name (JOIN Query)

Để khi gọi API lấy thông tin cá nhân (/api/user/me), chúng ta biết ngay User đó thuộc vai trò gì, hãy cập nhật lại hàm findById trong User.php.

File: app/Models/User.php (Cập nhật)

public function findById($id) {
    // Sử dụng LEFT JOIN để lấy tên vai trò từ bảng Roles
    $stmt = $this->db->prepare("
        SELECT u.*, r.name AS role_name
        FROM Users u
        LEFT JOIN Roles r ON u.role_id = r.id
        WHERE u.id = ?
    ");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

6. Kiểm thử với Curl

Tạo vai trò Admin:

curl -X POST http://localhost:8000/api/roles \
  -H "Content-Type: application/json" \
  -d '{"name":"admin"}'

Cập nhật vai trò ID số 2:

curl -X PUT http://localhost:8000/api/roles/2 \
  -H "Content-Type: application/json" \
  -d '{"name":"editor"}'

Lời kết cho Phần 8

Chúc mừng bạn! Hệ thống của chúng ta giờ đây đã có cấu trúc phân cấp rõ ràng hơn.

Chúng ta đã biết cách dùng Regex để xử lý Route động.

Chúng ta đã biết dùng JOIN để kết nối dữ liệu giữa các bảng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí