0

Giải Phẫu Từ Khóa static Trong PHP: Đừng Viết Code OOP Nếu Chưa Hiểu Rõ 3 Cảnh Giới Này!

Chào anh em! Nếu bạn là dân chơi hệ PHP/Laravel, chắc chắn bạn đã gõ từ khóa static hàng ngàn lần. Bạn thấy nó ở các hàm Helper, ở Eloquent Model (User::create()), hay ở các class Utility.

Nhưng bạn có thực sự hiểu điều gì xảy ra trong bộ nhớ (RAM) khi bạn gắn mác static cho một biến hay một hàm không? Nó không chỉ là "cách để gọi hàm mà không cần new object". Nó chứa đựng 3 "cảnh giới" hoàn toàn khác nhau. Hôm nay chúng ta sẽ bóc tách từng lớp một.

Cảnh Giới 1: static Properties & Methods (Tài Sản Chung Của Cả Dòng Họ)

Trong lập trình hướng đối tượng (OOP) bình thường, khi bạn tạo ra một Class (Bản thiết kế) và khởi tạo các Object (Thực thể), mỗi Object sẽ ôm một vùng nhớ riêng.

Hãy tưởng tượng bạn đang viết phần mềm cho hệ thống Cổng kiểm soát vé (AFC Gate). Nếu biến $status là bình thường (non-static), thì Cổng A hỏng không liên quan gì đến Cổng B.

Nhưng nếu bạn gắn static vào một thuộc tính, nó sẽ biến thành Tài sản chung của toàn bộ class đó. Tất cả các object sinh ra từ class đó đều "nhìn" vào chung một ô nhớ.

class Gate {
    public static $totalSwipes = 0; // Biến static
    public $gateName;

    public function __construct($name) {
        $this->gateName = $name;
    }

    public function swipeTicket() {
        self::$totalSwipes++; // Tăng biến chung
    }
}

$gateA = new Gate('Gate A');
$gateA->swipeTicket();

$gateB = new Gate('Gate B');
$gateB->swipeTicket();

echo Gate::$totalSwipes; // Kết quả: 2

Thực chiến: static method thường được dùng làm các hàm Utility (như Str::slug() trong Laravel) vì chúng không cần lưu trữ trạng thái riêng (state) của bất kỳ object nào. Gọi là chạy, chạy xong là nghỉ.

Cảnh Giới 2: Biến static Bên Trong Hàm (Kẻ Lưu Trữ Ký Ức)

Đây là một tính năng cực hay nhưng ít anh em xài. Thông thường, một biến cục bộ (local variable) bên trong hàm sẽ bị "tiêu diệt" sau khi hàm chạy xong.

Nhưng nếu bạn khai báo nó là static, nó sẽ sống sót và ghi nhớ giá trị của nó cho những lần gọi hàm tiếp theo, mà không hề làm bẩn không gian Global.

function processRefund() {
    static $refundCount = 0; // Chỉ khởi tạo đúng 1 lần đầu tiên
    
    $refundCount++;
    echo "Đã xử lý " . $refundCount . " vé hoàn tiền.\n";
}

processRefund(); // In ra: Đã xử lý 1 vé hoàn tiền.
processRefund(); // In ra: Đã xử lý 2 vé hoàn tiền.

Ứng dụng: Cực kỳ hữu ích khi bạn viết các hàm đệ quy (recursion), hoặc hàm cần đếm số lần được thực thi, hoặc làm một cache nội bộ siêu nhẹ ngay trong hàm mà không cần kéo Redis hay class vào.

Cảnh Giới 3: Late Static Binding (self:: vs static:😃 - Câu Hỏi Sinh Tử Khi Phỏng Vấn

Đây là cảnh giới phân biệt giữa Junior và Senior. Khi bạn kế thừa (Extend) các class, việc gọi phương thức tĩnh sẽ rất dễ bị "gậy ông đập lưng ông".

Hãy xem một ví dụ về BaseRepository – pattern kinh điển mà mọi dự án Laravel đều dùng:

class BaseModel {
    protected static $tableName = 'base_table';

    public static function getTableNameWithSelf() {
        return self::$tableName;
    }

    public static function getTableNameWithStatic() {
        return static::$tableName;
    }
}

class User extends BaseModel {
    protected static $tableName = 'users';
}

// Chuyện gì xảy ra ở đây?
echo User::getTableNameWithSelf();   // Kết quả: 'base_table' (Oái oăm chưa!)
echo User::getTableNameWithStatic(); // Kết quả: 'users' (Chuẩn!)

Tại sao lại như vậy?

  • self:: (Early Binding): Nó mù quáng trung thành với class chứa đoạn code của nó. Hàm getTableNameWithSelf() được viết ở BaseModel, nên chữ self luôn luôn trỏ về BaseModel, bất chấp class con có override lại hay không.
  • static:: (Late Static Binding): Nó thông minh hơn. Nó đợi đến lúc chương trình chạy (runtime) xem class nào đang thực sự gọi nó. Nếu User gọi, nó trỏ về User.

Kinh nghiệm xương máu: Khi viết các Base Class, Traits, hay Repositories để các class khác kế thừa, hãy luôn dùng static:: thay vì self:: để đảm bảo tính đa hình (Polymorphism) hoạt động đúng như mong đợi.

Lời Cảnh Báo: "Ma Túy" Của Unit Test và Long-Running Process

static rất tiện (đỡ phải new object lằng nhằng), nhưng đừng lạm dụng nó.

  1. Kẻ thù của Unit Test: Các hàm static dính chặt vào nhau (Tight Coupling), rất khó để giả lập (Mock) khi viết Test. Đó là lý do Laravel tạo ra hệ thống Facade (trông giống static nhưng bên dưới lại là Dependency Injection) để dễ test hơn.
  2. Memory Leak trong Laravel Octane / Swoole: Như đã nói ở bài static fn(), các biến static sẽ tồn tại vĩnh viễn trên RAM. Nếu bạn lưu một mảng dữ liệu vào thuộc tính static mà quên clear, thì sau 1 ngày chạy Octane, server của bạn sẽ báo Out Of Memory (OOM) và sập toàn tập.

Lời Kết

Từ khóa static giống như một con dao pha: nó sắc bén để chặt đứt những thao tác khởi tạo rườm rà, tạo ra những Design Pattern tuyệt vời như Singleton hay Factory. Nhưng nếu cầm không vững, bạn sẽ tự đứt tay bằng những bug liên quan đến State (trạng thái) và rò rỉ bộ nhớ.

Hiểu rõ 3 cảnh giới này, bạn đã bước một chân vào hàng ngũ những người thực sự làm chủ ngôn ngữ PHP. Chúc anh em code sạch và server luôn xanh!


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í