0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 6: Quản lý Hồ sơ & Cập nhật Thông tin Người dùng

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

Một hệ thống API "xịn" không chỉ dừng lại ở việc cho người dùng đăng nhập, mà còn phải cho phép họ làm chủ thông tin cá nhân của mình. Trong bài viết hôm nay, chúng ta sẽ xây dựng endpoint PUT /api/user/profile.

Tại sao lại là PUT? Trong chuẩn RESTful, PUT thường được dùng khi bạn muốn cập nhật toàn bộ tài nguyên (Resource). Hãy cùng xem cách chúng ta tận dụng Middleware để xác thực và "nhận diện" người dùng một cách an toàn nhất.

1. Tầng Model: Mở rộng khả năng tương tác

Chúng ta cần hai phương thức mới trong User.php: Một để cập nhật dữ liệu và một để lấy thông tin mới nhất sau khi cập nhật (để trả về cho Client).

File: app/Models/User.php (Bổ sung)

<?php
namespace App\Models;

use App\Core\Database;
use PDO;

class User {
    // ... (Các phương thức cũ: create, findByEmail, attemptLogin...)

    /**
     * Cập nhật thông tin chi tiết người dùng
     */
    public function updateProfile($id, $data) {
        $sql = "UPDATE Users 
                SET name = ?, 
                    phone_number = ?, 
                    address = ?, 
                    avatar = ?, 
                    updated_at = NOW() 
                WHERE id = ?";
        
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([
            $data['name'],
            $data['phone_number'],
            $data['address'],
            $data['avatar'],
            $id
        ]);
    }

    /**
     * Tìm người dùng theo ID (Dùng để trả về data sau khi update)
     */
    public function findById($id) {
        $stmt = $this->db->prepare("SELECT id, name, email, phone_number, address, avatar, status FROM Users WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch();
    }
}

2. Tầng Controller: Xử lý logic nghiệp vụ

Chúng ta sẽ tạo một UserController riêng biệt để quản lý các hành động liên quan đến thông tin user, tách biệt khỏi AuthController.

File: app/Controllers/UserController.php

<?php
namespace App\Controllers;

use App\Core\Response;
use App\Models\User;
use App\Middleware\AuthMiddleware;

class UserController
{
    public function updateProfile() {
        // 1. Đọc dữ liệu từ Request Body
        $data = json_decode(file_get_contents("php://input"), true);
        
        // Validation cơ bản các trường bắt buộc
        $required = ['name', 'phone_number', 'address', 'avatar'];
        $missing = array_filter($required, fn($field) => empty($data[$field]));

        if (!empty($missing)) {
            Response::json([
                'error' => 'Vui lòng điền đầy đủ các trường', 
                'missing_fields' => array_values($missing)
            ], 422);
        }

        /**
         * 2. Xác thực và lấy thông tin từ Token
         * AuthMiddleware::check() sẽ trả về Payload của JWT nếu token hợp lệ
         */
        $userPayload = AuthMiddleware::check();
        $userId = $userPayload->sub; // 'sub' chứa ID người dùng mà ta đã lưu ở Phần 3

        $userModel = new User();
        
        // 3. Thực hiện cập nhật
        $success = $userModel->updateProfile($userId, $data);

        if ($success) {
            // Lấy lại dữ liệu mới nhất để trả về cho Client
            $updatedUser = $userModel->findById($userId);
            Response::json([
                'message' => 'Cập nhật hồ sơ thành công!', 
                'user' => $updatedUser
            ]);
        } else {
            Response::json(['error' => 'Có lỗi xảy ra trong quá trình cập nhật'], 500);
        }
    }
}

3. Cấu hình Route mới

Đừng quên đăng ký Endpoint này vào "bản đồ" của ứng dụng. Lưu ý rằng chúng ta dùng phương thức PUT.

File: public/index.php

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

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

$userController = new UserController();

// ... Các route cũ ...

// Route cập nhật profile (Yêu cầu JWT Token trong Header)
if ($uri === '/api/user/profile' && $method === 'PUT') {
    $userController->updateProfile();
} 
// ...

4. Kiểm thử với Curl

Bây giờ là lúc kiểm tra thành quả. Bạn cần có một mã JWT Token hợp lệ (lấy từ API Đăng nhập ở Phần 3).

curl -X PUT http://localhost:8000/api/user/profile \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <DÁN_JWT_CỦA_BẠN_VÀO_ĐÂY>" \
  -d '{
    "name": "Nguyễn Văn B (Updated)",
    "phone_number": "0912345678",
    "address": "Phường Tân Thới Nhất, Quận 12, HCM",
    "avatar": "https://hasaki.vn/logo.png"
}'

Gợi ý nâng cao cho dự án thực tế

Trong môi trường làm việc chuyên nghiệp (như tại Hasaki hay các Tech Hub lớn), tính năng này thường được mở rộng thêm:

Partial Update (PATCH): Thay vì bắt gửi lại toàn bộ thông tin, bạn có thể dùng phương thức PATCH để chỉ cập nhật những trường người dùng muốn thay đổi.

Avatar Upload: Hiện tại chúng ta đang lưu avatar dưới dạng một link URL. Trong các bài tới, mình sẽ hướng dẫn các bạn cách xử lý Upload File trực tiếp lên Server hoặc S3 để làm ảnh đại diện thực thụ.

Password Change: Đừng gộp việc đổi mật khẩu vào hàm update thông tin chung. Hãy tạo một endpoint riêng (/api/change-password) với yêu cầu nhập mật khẩu cũ để đảm bảo tính an toàn.

Tạm kết

Vậy là chúng ta đã hoàn thành mảnh ghép tiếp theo của hệ thống. API của chúng ta giờ đây đã "thông minh" hơn khi biết kết hợp giữa Xác thực và Cập nhật dữ liệu.


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í