0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 15: Chi tiết Sản phẩm & Bộ sưu tập Hình ảnh (Product Gallery)

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

Khi người dùng click vào một sản phẩm từ danh sách, hệ thống cần trả về toàn bộ "nội thất" của sản phẩm đó. Một lỗi phổ biến của các bạn mới làm là chỉ trả về dữ liệu trong bảng Products. Thực tế, một sản phẩm thường có nhiều ảnh (Gallery).

Trong bài này, mình sẽ hướng dẫn cách truy vấn lồng (hoặc tách biệt) để lấy toàn bộ thông tin sản phẩm và danh sách ảnh phụ chỉ trong một nốt nhạc.

1. Tầng Model: Gom dữ liệu từ nhiều bảng (Product.php)

Chúng ta sẽ nâng cấp hàm findById. Sau khi lấy được thông tin cơ bản và category_name, chúng ta sẽ thực hiện thêm một câu query nhỏ để lấy mảng hình ảnh từ bảng Product_Images.

File: app/Models/Product.php

<?php
namespace App\Models;

use App\Core\Database;

class Product
{
    protected $db;

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

    /**
     * Tìm chi tiết sản phẩm kèm danh mục và bộ sưu tập ảnh
     */
    public function findById($id) {
        // 1. Lấy thông tin cơ bản và tên danh mục
        $stmt = $this->db->prepare("
            SELECT p.*, c.name AS category_name
            FROM Products p
            LEFT JOIN Categories c ON p.category_id = c.id
            WHERE p.id = ?
        ");
        $stmt->execute([$id]);
        $product = $stmt->fetch();

        if ($product) {
            // 2. Lấy danh sách hình ảnh phụ từ bảng Product_Images
            $imgStmt = $this->db->prepare("SELECT image FROM Product_Images WHERE product_id = ?");
            $imgStmt->execute([$id]);
            
            // array_column giúp chuyển mảng đa chiều thành mảng phẳng chứa link ảnh
            $product['images'] = array_column($imgStmt->fetchAll(), 'image');
        }

        return $product;
    }
}

2. Tầng Controller: Điều phối dữ liệu (ProductController.php)

Controller sẽ nhận ID từ Router, gọi Model và kiểm tra xem sản phẩm có tồn tại hay không. Nếu không, phải trả về mã lỗi 404 Not Found đúng chuẩn RESTful.

File: app/Controllers/ProductController.php (Bổ sung method show)

<?php
namespace App\Controllers;

use App\Models\Product;
use App\Core\Response;

class ProductController
{
    // ... (Hàm index lấy danh sách ở phần trước)

    public function show($id) {
        $productModel = new Product();
        $product = $productModel->findById($id);

        if (!$product) {
            Response::json([
                'status' => 'error',
                'message' => 'Sản phẩm không tồn tại hoặc đã bị xóa'
            ], 404);
        }

        Response::json([
            'status' => 'success',
            'data' => $product
        ]);
    }
}

3. Cấu hình Route Động (index.php)

Chúng ta sử dụng Regex để bắt ID sản phẩm từ URL.

File: public/index.php

use App\Controllers\ProductController;

$productController = new ProductController();

// Route Chi tiết sản phẩm: /api/products/{id}
if (preg_match('#^/api/products/(\d+)$#', $uri, $matches) && $method === 'GET') {
    $productId = $matches[1];
    $productController->show($productId);
}

4. Kiểm thử API (Test with Curl)

Thử lấy thông tin của sản phẩm có ID là 5:

curl http://localhost:8000/api/products/5

Kết quả mong đợi:

{
  "status": "success",
  "data": {
    "id": 5,
    "name": "iPhone 15 Pro Max",
    "price": 39990000,
    "category_name": "Điện thoại",
    "images": [
      "https://cdn.hasaki.vn/products/ip15-front.jpg",
      "https://cdn.hasaki.vn/products/ip15-side.jpg"
    ]
  }
}

Góc nhìn chuyên gia: Tối ưu cho SEO và UX

Search by Slug: Thay vì /api/products/5, người dùng thích /api/products/iphone-15-pro-max hơn. Bạn có thể viết thêm hàm findBySlug($slug) trong Model để hỗ trợ SEO.

Related Products: Ở cuối trang chi tiết, luôn có phần "Sản phẩm tương tự". Bạn nên viết thêm một hàm lấy các sản phẩm có cùng category_id nhưng loại trừ ID hiện tại.

Caching: Thông tin chi tiết sản phẩm ít khi thay đổi liên tục. Đây là ứng viên sáng giá để áp dụng Redis Cache nhằm giảm tải cho MySQL.

Tạm kết

Vậy là hệ thống của chúng ta đã có thể trình diễn đầy đủ thông tin sản phẩm cho khách hàng. Đây là một bước tiến lớn trong hành trình xây dựng ứng dụng E-commerce.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.