Sửa lỗi PHP Fatal error: Call to a member function on null

intermediate🐘 PHP2026-04-22| PHP 7.0+ / PHP 8.x, mọi hệ điều hành (Linux, macOS, Windows), mọi framework (Laravel, Symfony, WordPress, PHP thuần)

Error Message

PHP Fatal error: Uncaught Error: Call to a member function [function_name]() on null
#php#fatal error#null pointer#object

Lỗi Gặp Phải

Ứng dụng PHP của bạn đang chạy bình thường — cho đến khi không còn bình thường nữa. Bạn kiểm tra log và thấy dòng này:

PHP Fatal error: Uncaught Error: Call to a member function getName() on null
in /var/www/html/app/Controllers/UserController.php:42

Tên phương thức sẽ khác nhau tùy dự án. Nhưng cấu trúc lỗi thì không bao giờ thay đổi: bạn đã gọi một phương thức trên một biến đang chứa null thay vì một object. PHP dừng hẳn. Không có cơ chế khôi phục, không có output một phần — chỉ là một fatal error.

Nguyên Nhân Gốc Rễ

Ở đâu đó giữa lúc gán giá trị và lúc gọi phương thức, biến của bạn đã mất đi object của nó. Một vài tình huống chiếm phần lớn các trường hợp:

  • Một câu truy vấn cơ sở dữ liệu không trả về kết quả (null hoặc rỗng), và bạn đã gọi phương thức trên đó mà không kiểm tra trước.
  • Một factory method hoặc DI container trả về null do thiếu binding hoặc cấu hình sai.
  • Một thuộc tính của class chưa bao giờ được khởi tạo trước khi sử dụng.
  • Một hàm trả về null trên một nhánh code mà bạn quên xử lý — phổ biến với json_decode() hoặc preg_match().
  • Một lời gọi new ClassName() thất bại trong im lặng vì constructor tùy chỉnh nuốt ngoại lệ.

Bước 1: Xác Định Chính Xác Biến Nào Đang Null

Đừng đoán mò. Đi thẳng đến dòng PHP báo lỗi và dump biến ngay trước lời gọi gây ra lỗi:

<?php
// Lỗi xảy ra ở đây:
$user->getName();

// Thêm dòng này ngay phía trên:
var_dump($user); // NULL? Đó là thủ phạm.
die();

Sau khi xác nhận biến nào đang null, hãy truy ngược về nơi nó được gán giá trị. Dòng gán — không phải dòng gọi phương thức — mới là nơi vấn đề thực sự nằm.

Cách Sửa 1: Kiểm Tra Null Trước Khi Dùng

Khi null là một trạng thái hợp lệ (người dùng có thể không tồn tại, bản ghi có thể đã bị xóa), một kiểm tra tường minh là lựa chọn đúng đắn:

<?php
$user = getUserById($id);

if ($user !== null) {
    echo $user->getName();
} else {
    echo 'User not found';
}

Đang dùng PHP 8.0 trở lên? Toán tử nullsafe giúp viết gọn thành một dòng:

<?php
// Nếu $user là null, getName() sẽ không bao giờ được gọi — trả về null thay vào đó
$name = $user?->getName();
echo $name ?? 'User not found';

Toán tử nullsafe sẽ dừng toàn bộ chuỗi gọi. Không có object, không có lời gọi phương thức, không có fatal error.

Cách Sửa 2: Sửa Query Hoặc Nguồn Dữ Liệu Trả Về Null

Thường thì việc kiểm tra null chỉ là miếng băng dán. Câu hỏi thực sự là: tại sao nguồn dữ liệu của bạn lại không trả về gì?

<?php
// Sai: findById() trả về null khi ID không tồn tại
$user = User::findById($id);
$user->getName(); // Lỗi ngay — nếu $id là 0, chuỗi rỗng, hoặc bản ghi đã bị xóa

// Cách A: findOrFail() ném ngoại lệ rõ ràng thay vì trả về null
$user = User::findOrFail($id); // Ném ModelNotFoundException nếu không tìm thấy
$user->getName(); // An toàn

// Cách B: dùng object mặc định làm fallback
$user = User::findById($id) ?? new GuestUser();
$user->getName();

Kiểm tra tham số truy vấn của bạn trước. Một chuỗi rỗng, số không, hoặc ID cũ từ một bản ghi đã bị xóa chiếm khoảng 80% các trường hợp.

Cách Sửa 3: Khởi Tạo Thuộc Tính Object Trước Khi Sử Dụng

Thuộc tính class chưa được khởi tạo là một cái bẫy âm thầm. Thuộc tính tồn tại — nó chỉ đang chứa null cho đến khi có gì đó gán giá trị cho nó:

<?php
class OrderProcessor
{
    private ?Logger $logger = null; // Khai báo nhưng chưa được inject

    public function process(): void
    {
        $this->logger->log('Processing...'); // Fatal nếu logger chưa bao giờ được set
    }
}

// Cách A: yêu cầu dependency trong constructor
class OrderProcessor
{
    private Logger $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function process(): void
    {
        $this->logger->log('Processing...');
    }
}

// Cách B: khởi tạo lười với fallback
public function process(): void
{
    if ($this->logger === null) {
        $this->logger = new NullLogger();
    }
    $this->logger->log('Processing...');
}

Cách Sửa 4: Xử Lý Giá Trị Null Từ Các Hàm Bên Ngoài

Các hàm thư viện chuẩn gây rắc rối ở đây nhiều hơn bạn nghĩ. json_decode() là ví dụ điển hình:

<?php
// json_decode() trả về null khi parse thất bại — JSON sai định dạng, chuỗi rỗng, bất kỳ lý do gì
$data = json_decode($response);
$data->status; // Fatal nếu $response là JSON không hợp lệ

// Sửa: kiểm tra json_last_error() trước khi dùng kết quả
$data = json_decode($response);
if (json_last_error() !== JSON_ERROR_NONE || $data === null) {
    throw new \RuntimeException('Invalid JSON: ' . json_last_error_msg());
}
echo $data->status; // An toàn

// PHP 8: preg_match() cũng có thể trả về null khi có lỗi regex
$result = preg_match('/pattern/', $subject, $matches);
if ($result === false || $result === null) {
    // xử lý lỗi regex
}

Cách Sửa 5: Vấn Đề Dependency Injection / Service Container

Trong Laravel hoặc Symfony, một binding container bị thiếu sẽ tạo ra chính xác lỗi này. Signature của constructor trông ổn — nhưng object được inject chưa bao giờ được đăng ký:

<?php
// Controller Laravel
public function __construct(private UserRepository $repo)
{
    // $repo là null nếu UserRepository không có binding
}

// Đăng ký trong AppServiceProvider:
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(UserRepository::class, EloquentUserRepository::class);
}

Sau khi thay đổi service binding, hãy xóa cache của container. Cache cũ nghĩa là cấu hình cũ (bị lỗi) vẫn còn hiệu lực:

php artisan clear-compiled
php artisan optimize:clear

Phòng Ngừa

Bốn thói quen giúp loại bỏ lỗi này khỏi codebase của bạn vĩnh viễn:

  • Khai báo kiểu trả về chặt chẽ. Dùng : User thay vì : ?User khi null không phải là giá trị trả về hợp lệ. PHP sẽ ném TypeError ngay tại nguồn — không phải một fatal error khó hiểu ở năm lớp gọi tiếp theo.
  • strict_types=1. Thêm declare(strict_types=1); ở đầu mỗi file. Các sai lệch kiểu dữ liệu sẽ xuất hiện sớm, trước khi chúng tích lũy thành vấn đề lớn.
  • Phân tích tĩnh. PHPStan ở level 5 trở lên phát hiện lỗi null dereference trước khi bạn chạy một dòng code nào. Psalm cũng làm được điều tương tự. Cả hai đều không tốn chi phí runtime.
  • Ném ngoại lệ, đừng trả về null. Các phương thức không thể hoàn thành công việc nên ném ngoại lệ. Null âm thầm che giấu bug; ngoại lệ phơi bày chúng.

Xác Nhận Đã Sửa Xong

Tái hiện lỗi ban đầu trước, rồi xác nhận nó đã biến mất:

# 1. Kích hoạt đúng tình huống đã gây ra lỗi
php artisan tinker
>>> $user = App\Models\User::find(99999); // ID không tồn tại
>>> $user?->getName(); // Nên trả về null, không ném ngoại lệ

# 2. Chạy các test liên quan
php artisan test --filter UserTest

# 3. Xác nhận log lỗi đã sạch
tail -f /var/log/php/error.log
# hoặc với Laravel:
tail -f storage/logs/laravel.log

Không còn fatal error, nhánh null trả về giá trị fallback mong đợi — bạn đã xong.

Related Error Notes