Sửa lỗi PHP Fatal Error: Typed property must not be accessed before initialization

intermediate🐘 PHP2026-05-24| PHP 7.4, PHP 8.0, PHP 8.1, PHP 8.2, PHP 8.3+ trên Linux, Windows, hoặc macOS

Error Message

Fatal error: Uncaught Error: Typed property Foo::$bar must not be accessed before initialization
#php#php8#typed-property#fatal-error#oop

Tại sao mã nguồn của bạn gặp lỗi: Trạng thái "Chưa khởi tạo" (Uninitialized)

PHP 7.4 đã giới thiệu các thuộc tính có kiểu dữ liệu (typed properties), nhưng PHP 8 thực sự thắt chặt cách chúng ta xử lý chúng. Các thuộc tính không định kiểu thông thường luôn mặc định là null. Tuy nhiên, các thuộc tính định kiểu tồn tại ở một trạng thái "zombie" gọi là uninitialized (chưa khởi tạo) cho đến khi bạn gán giá trị rõ ràng cho chúng. Nếu bạn cố gắng đọc giá trị trước khi điều đó xảy ra, PHP sẽ không chỉ đưa ra cảnh báo—nó sẽ dừng kịch bản của bạn ngay lập tức với một lỗi fatal.

Fatal error: Uncaught Error: Typed property Foo::$bar must not be accessed before initialization

Lỗi này thường xảy ra với các lập trình viên trong quá trình nâng cấp lên PHP 8 hoặc khi họ lầm tưởng rằng một thuộc tính cho phép null (như ?string) sẽ tự động mặc định là null. Thực tế không phải vậy. Bạn cần phải chủ động kiểm soát dữ liệu của mình.

Ví dụ về đoạn mã gây lỗi

<?php
class UserProfile {
    public string $username;
    public int $age;
}

$profile = new UserProfile();
echo $profile->username; // ❌ Lỗi: Fatal Error xảy ra tại đây
?>

Cách khắc phục lỗi

Bạn có thể xử lý vấn đề này theo một vài cách khác nhau, tùy thuộc vào cấu trúc lớp của bạn.

1. Giải pháp tối ưu nhất: Constructor Property Promotion

PHP hiện đại giúp việc định nghĩa và khởi tạo thuộc tính trở nên dễ dàng hơn. Kể từ PHP 8.0, bạn có thể sử dụng tính năng "constructor property promotion" để đảm bảo mọi đối tượng đều bắt đầu với dữ liệu hợp lệ. Phương pháp này súc tích và loại bỏ các mã lặp (boilerplate).

<?php
class UserProfile {
    // Định nghĩa và gán giá trị trên cùng một dòng
    public function __construct(
        public string $username = "Guest",
        public int $age = 0
    ) {}
}

$profile = new UserProfile();
echo $profile->username; // Hoạt động: xuất ra "Guest"
?>

2. Cách nhanh nhất: Giá trị mặc định trực tiếp (Inline)

Nếu bạn không cần truyền giá trị qua hàm khởi tạo (constructor), chỉ cần gán một giá trị mặc định ngay trong định nghĩa lớp. Điều này ngay lập tức đưa thuộc tính ra khỏi trạng thái "chưa khởi tạo" nguy hiểm.

<?php
class AppConfig {
    public string $theme = 'light';
    public bool $enabled = false;
}

$config = new AppConfig();
echo $config->theme; // Hoạt động: xuất ra "light"
?>

3. Lưu ý về kiểu Nullable

Việc thêm dấu hỏi chấm vào một kiểu dữ liệu (như ?string) giúp nó có thể nhận giá trị null, nhưng nó vẫn sẽ không tự động mặc định là null. Bạn phải thiết lập nó một cách rõ ràng. Đây là một sai lầm phổ biến khi nâng cấp mã nguồn từ phiên bản 7.x.

<?php
class Document {
    // ❌ Vẫn gây lỗi nếu truy cập ngay lập tức
    // public ?string $title;

    // ✅ An toàn: Đã được khởi tạo với giá trị null
    public ?string $title = null;
}

$doc = new Document();
var_dump($doc->title); // Hoạt động: xuất ra NULL
?>

4. Kiểm tra "Tức thời" với isset()

Đôi khi bạn không thể cung cấp giá trị ngay lập tức—có thể nó cần được lấy từ cơ sở dữ liệu sau đó. Trong những trường hợp này, hãy sử dụng isset(). Trong PHP, isset() sẽ trả về false đối với các thuộc tính chưa được khởi tạo, cho phép bạn chỉ tải dữ liệu khi cần thiết.

<?php
class LazyLoader {
    public string $data;

    public function getData(): string {
        if (!isset($this->data)) {
            $this->data = $this->fetchFromApi(); // Ví dụ cụ thể về lazy loading
        }
        return $this->data;
    }

    private function fetchFromApi(): string {
        return "Dữ liệu quan trọng đã được tải";
    }
}
?>

Kiểm tra giải pháp

Hãy chạy thử trong terminal để đảm bảo lỗi đã biến mất. Nếu kịch bản kết thúc mà không có Uncaught Error, bạn đã quản lý thành công trạng thái của thuộc tính.

# Chạy kịch bản của bạn
php your_script.php

# Kết quả thành công sẽ như thế này:
string(5) "Guest"

Lời khuyên chuyên gia cho mã PHP tốt hơn

- **Chặt chẽ với giá trị Null:** Chỉ nên để thuộc tính là nullable nếu giá trị `null` thực sự có ý nghĩa trong logic nghiệp vụ của bạn. Nếu một người dùng bắt buộc phải có tên, hãy yêu cầu nó thông qua hàm khởi tạo.
- **Tự động hóa việc kiểm tra:** Sử dụng PHPStan ở mức độ 6 hoặc cao hơn. Công cụ này sẽ quét toàn bộ dự án và chỉ ra chính xác nơi bạn quên khởi tạo thuộc tính trước khi bạn nhấn lưu.
- **Chú ý các bộ ánh xạ (Mappers):** Nếu bạn sử dụng một ORM như Doctrine, hãy đảm bảo cấu trúc cơ sở dữ liệu khớp với kiểu dữ liệu trong lớp của bạn. Một cột trống trong cơ sở dữ liệu cố gắng đổ dữ liệu vào một thuộc tính không cho phép null sẽ gây ra chính lỗi này.

Related Error Notes