[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 25: API Theo dõi hành trình đơn hàng (Order Tracking Timeline)
Chào các bạn, mình đã quay trở lại!
Ở bài trước, chúng ta đã học cách ghi lại nhật ký mỗi khi đổi trạng thái. Nhưng ghi vào rồi thì phải lấy ra để hiển thị cho người dùng xem chứ nhỉ? Một điểm cực kỳ quan trọng trong bài này là Bảo mật truy vấn (Access Control). Bạn phải đảm bảo chỉ chủ nhân của đơn hàng đó mới có quyền xem lịch sử di chuyển của nó.
1. Tư duy lập trình: JOIN để xác thực quyền sở hữu
Tại sao trong Model mình lại yêu cầu JOIN với bảng Orders thay vì chỉ SELECT thẳng từ bảng Order_Status_History?
Bảng Order_Status_History chỉ chứa order_id.
Để biết order_id đó có phải của bạn không, chúng ta cần bắc cầu qua bảng Orders để kiểm tra user_id.
2. Tầng Model: Truy vấn hành trình (Order.php)
Chúng ta bổ sung hàm lấy lịch sử, sắp xếp theo thời gian tăng dần (ASC) để tạo thành một dòng thời gian (Timeline) từ cũ đến mới.
File: app/Models/Order.php (Bổ sung)
<?php
namespace App\Models;
// ... (Các hàm đã viết: create, updateStatus, findById...)
/**
* Lấy lịch sử thay đổi trạng thái của một đơn hàng
* Phải JOIN với bảng Orders để kiểm tra quyền sở hữu của User
*/
public function getStatusHistory($orderId, $userId) {
$stmt = $this->db->prepare("
SELECT osh.status, osh.note, osh.changed_at
FROM Order_Status_History osh
JOIN Orders o ON o.id = osh.order_id
WHERE osh.order_id = ? AND o.user_id = ?
ORDER BY osh.changed_at ASC
");
$stmt->execute([$orderId, $userId]);
return $stmt->fetchAll();
}
3. Tầng Controller: Xử lý Logic Tracking (OrderController.php)
Controller sẽ nhận yêu cầu, lấy ID người dùng từ JWT và trả về dữ liệu Timeline.
File: app/Controllers/OrderController.php (Bổ sung)
<?php
namespace App\Controllers;
use App\Core\Response;
use App\Models\Order;
use App\Middleware\AuthMiddleware;
class OrderController
{
/**
* API: Lấy dòng thời gian (Timeline) trạng thái đơn hàng
*/
public function statusHistory($id) {
// 1. Xác thực người dùng
$user = AuthMiddleware::check();
$model = new Order();
// 2. Lấy dữ liệu lịch sử kèm theo check user_id trong Model
$history = $model->getStatusHistory($id, $user->sub);
if (empty($history)) {
// Nếu không có lịch sử hoặc không phải đơn hàng của user này
Response::json(['error' => 'Không tìm thấy thông tin hành trình đơn hàng'], 404);
}
// 3. Trả về mảng history cho Frontend vẽ Timeline
Response::json([
'status' => 'success',
'order_id' => $id,
'history' => $history
]);
}
}
4. Cấu hình Route (index.php)
Đăng ký Endpoint mới cho tính năng Tracking.
File: public/index.php
use App\Controllers\OrderController;
$orderController = new OrderController();
// API Xem lịch sử trạng thái: GET /api/orders/{id}/status-history
if (preg_match('#^/api/orders/(\d+)/status-history$#', $uri, $matches) && $method === 'GET') {
$orderController->statusHistory($matches[1]);
}
5. Kiểm thử API (Test with Curl)
Bạn hãy thử lấy lịch sử của đơn hàng số 15 mà chúng ta đã thao tác ở các bài trước:
curl -X GET http://localhost:8000/api/orders/15/status-history \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Output mong đợi:
{
"status": "success",
"order_id": "15",
"history": [
{
"status": "pending",
"note": "Đơn hàng vừa được tạo thành công",
"changed_at": "2026-03-25 14:21:00"
},
{
"status": "processing",
"note": "Hasaki đang đóng gói sản phẩm của bạn",
"changed_at": "2026-03-25 16:10:00"
}
]
}
Tạm kết
Vậy là bạn đã hoàn thành một trong những module quan trọng nhất của quản lý đơn hàng. Khách hàng giờ đây đã có thể yên tâm theo dõi "đứa con tinh thần" của mình đang ở đâu trên bản đồ giao hàng. Hãy để lại bình luận nhé! Đừng quên Upvote để tiếp thêm động lực cho mình. Chúc bạn code vui vẻ, bố đời!
All rights reserved