0

[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 13: Bộ lọc Sản phẩm chuyên sâu (Search by Name, Slug, Category)

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

Ở bài trước, chúng ta đã làm quen với việc lấy danh sách sản phẩm cơ bản. Nhưng thực tế, khách hàng thường tìm kiếm theo nhiều cách khác nhau: "iPhone 15", "apple", hay thậm chí là gõ "điện thoại" để xem toàn bộ danh mục.

Hôm nay, chúng ta sẽ "buff" thêm sức mạnh cho hàm getAll() bằng kỹ thuật ghép nối SQL năng động. Mục tiêu là giúp Frontend chỉ cần gọi một Endpoint duy nhất mà vẫn xử lý được mọi yêu cầu lọc phức tạp.

1. Tầng Model: Nâng cấp Search Engine "Cây nhà lá vườn"

Chúng ta sẽ sử dụng kỹ thuật "WHERE 1" để nối các điều kiện AND một cách linh hoạt. Đặc biệt, chúng ta sẽ dùng toán tử OR để tìm kiếm đồng thời trên cả nameslug.

File: app/Models/Product.php (Cập nhật hàm getAll)

<?php
namespace App\Models;

use App\Core\Database;

class Product
{
    protected $db;

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

    public function getAll($filters = []) {
        // Khởi tạo câu truy vấn cơ bản với JOIN để lấy tên danh mục
        $sql = "SELECT p.*, c.name AS category_name
                FROM Products p
                LEFT JOIN Categories c ON p.category_id = c.id
                WHERE 1"; 

        $params = [];

        // 🔍 1. Tìm kiếm theo Tên hoặc Slug (SEO Friendly)
        if (!empty($filters['search'])) {
            $sql .= " AND (p.name LIKE ? OR p.slug LIKE ?)";
            $searchTerm = "%" . $filters['search'] . "%";
            $params[] = $searchTerm;
            $params[] = $searchTerm;
        }

        // 🔎 2. Lọc theo ID danh mục (Chính xác tuyệt đối)
        if (!empty($filters['category_id'])) {
            $sql .= " AND p.category_id = ?";
            $params[] = $filters['category_id'];
        }

        // 📦 3. Lọc theo Tên danh mục (Dành cho việc search theo từ khóa ngành hàng)
        if (!empty($filters['category'])) {
            $sql .= " AND c.name LIKE ?";
            $params[] = "%" . $filters['category'] . "%";
        }

        // 📄 4. Phân trang & Sắp xếp
        $limit = isset($filters['limit']) ? (int)$filters['limit'] : 10;
        $page = isset($filters['page']) ? (int)$filters['page'] : 1;
        $offset = ($page - 1) * $limit;

        $sql .= " ORDER BY p.created_at DESC LIMIT $limit OFFSET $offset";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return $stmt->fetchAll();
    }
}

2. Tầng Controller: Điều phối Query Parameters

Tại ProductController, chúng ta sẽ hứng các tham số từ $_GET và "đóng gói" chúng vào một mảng $filters trước khi gửi xuống Model.

File: app/Controllers/ProductController.php

<?php
namespace App\Controllers;

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

class ProductController
{
    public function index() {
        // Thu thập toàn bộ các tiêu chí lọc từ URL
        $filters = [
            'search'      => $_GET['search'] ?? null,
            'category_id' => $_GET['category_id'] ?? null,
            'category'    => $_GET['category'] ?? null,
            'limit'       => $_GET['limit'] ?? 10,
            'page'        => $_GET['page'] ?? 1,
        ];

        $productModel = new Product();
        $products = $productModel->getAll($filters);

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

3. Kiểm thử sức mạnh bộ lọc (Test with Curl)

Hãy thử các kịch bản thực tế sau:

Tìm kiếm theo tên/slug:

curl "http://localhost:8000/api/products?search=macbook"

Lọc theo ID danh mục (Ví dụ ID: 2 là Điện thoại):

curl "http://localhost:8000/api/products?category_id=2"

Tìm sản phẩm Laptop ở trang thứ 2:

curl "http://localhost:8000/api/products?category=laptop&page=2&limit=5"

Tạm kết cho Phần 13

Chúng ta đã hoàn thiện một bộ máy tìm kiếm sản phẩm linh hoạt, giúp người dùng dễ dàng tiếp cận sản phẩm họ cần. Đây là nền tảng cực kỳ quan trọng để Frontend có thể xây dựng các trang danh mục sản phẩm (Category Page) và trang tìm kiếm (Search Page).

Hãy để lại bình luận phía dưới nhé! Đừng quên Upvote để ủng hộ mình ra bài đều đặn. Chúc các bạn code vui vẻ!


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í