Bạn đã bao giờ thắc mắc tại sao khi tạo một đối tượng mới từ một class trong PHP, một số thao tác lại được thực hiện tự động không? Đó chính là nhờ “phép màu” của hàm __construct – một trong những khái niệm cơ bản nhất trong lập trình hướng đối tượng (OOP) mà mọi PHP developer đều cần nắm vững.

Hàm __construct trong PHP là một phương thức đặc biệt được gọi tự động ngay khi một đối tượng (object) được tạo ra từ một class. Nói đơn giản hơn, đây chính là “trái tim” của quá trình tạo đối tượng, đảm bảo mọi thứ được thiết lập đúng cách ngay từ những giây phút đầu tiên.
Tại sao tôi gọi nó là “trái tim”? Bởi vì giống như trái tim con người bắt đầu đập ngay khi chúng ta sinh ra, hàm __construct cũng bắt đầu hoạt động ngay khi đối tượng được “sinh ra” trong bộ nhớ. Không cần gọi thủ công, không cần nhớ tên phức tạp – mọi thứ diễn ra một cách tự nhiên và hiệu quả.
Trong bài viết hôm nay, chúng ta sẽ cùng khám phá chi tiết về hàm __construct từ A đến Z. Bạn sẽ học được cách khai báo và sử dụng hàm này một cách chính xác, hiểu rõ vai trò quan trọng của nó trong lập trình hướng đối tượng, nắm vững cách áp dụng trong kế thừa (inheritance), và tránh được những lỗi phổ biến mà nhiều developer mới bắt đầu thường mắc phải. Cuối cùng, tôi sẽ chia sẻ với bạn những best practices để viết code PHP chuyên nghiệp và dễ bảo trì.
Khai báo hàm __construct đơn giản
Để hiểu rõ cách hoạt động của hàm __construct, chúng ta hãy bắt đầu với một ví dụ đơn giản nhất. Cú pháp khai báo hàm __construct trong PHP khá straightforward và dễ nhớ.

<?php
class SanPham {
public $ten;
public $gia;
public function __construct() {
echo "Một sản phẩm mới đã được tạo!";
$this->ten = "Sản phẩm mặc định";
$this->gia = 0;
}
}
// Tạo đối tượng mới
$sanPham1 = new SanPham();
// Kết quả: "Một sản phẩm mới đã được tạo!" sẽ được hiển thị
?>
Như bạn có thể thấy, hàm __construct được khai báo giống như một phương thức bình thường, nhưng có tên đặc biệt bắt đầu bằng hai dấu gạch dưới (__). Điều quan trọng cần nhớ là tên này phải được viết chính xác – không được thiếu dấu gạch dưới hay viết sai chính tả.
Truyền tham số vào hàm khởi tạo
Sức mạnh thực sự của hàm __construct được thể hiện khi chúng ta có thể truyền tham số vào để khởi tạo đối tượng với dữ liệu cụ thể. Đây là nơi tính linh hoạt của PHP thực sự tỏa sáng.
<?php
class SanPham {
public $ten;
public $gia;
public $moTa;
public function __construct($ten, $gia, $moTa = "Không có mô tả") {
$this->ten = $ten;
$this->gia = $gia;
$this->moTa = $moTa;
echo "Sản phẩm '$ten' với giá $gia VND đã được tạo thành công!";
}
public function hienThiThongTin() {
return "Tên: {$this->ten}, Giá: {$this->gia} VND, Mô tả: {$this->moTa}";
}
}
// Tạo các đối tượng với dữ liệu khác nhau
$laptop = new SanPham("Laptop Dell XPS", 25000000, "Laptop cao cấp cho doanh nhân");
$dienthoai = new SanPham("iPhone 15", 30000000);
echo $laptop->hienThiThongTin();
echo $dienthoai->hienThiThongTin();
?>

Ví dụ trên cho thấy cách chúng ta có thể truyền dữ liệu động vào hàm __construct. Tham số thứ ba ($moTa) có giá trị mặc định, nghĩa là nếu không truyền vào, nó sẽ sử dụng giá trị “Không có mô tả”. Điều này giúp code trở nên linh hoạt và dễ sử dụng hơn.
Tự động khởi tạo đối tượng khi gọi lớp
Một trong những điều tuyệt vời nhất của hàm __construct là tính tự động của nó. Bạn có nhớ thời gian trước khi có hàm __construct, các developer phải thực hiện những thao tác khởi tạo thủ công không? Điều đó không chỉ tốn thời gian mà còn dễ dẫn đến lỗi khi quên gọi hàm khởi tạo.

<?php
// Cách cũ - không dùng __construct
class KhachHangCu {
public $ten;
public $email;
public function khoiTao($ten, $email) {
$this->ten = $ten;
$this->email = $email;
// Có thể quên gọi hàm này!
}
}
// Cách mới - dùng __construct
class KhachHang {
public $ten;
public $email;
public $ngayTao;
public function __construct($ten, $email) {
$this->ten = $ten;
$this->email = $email;
$this->ngayTao = date('Y-m-d H:i:s');
$this->guiEmailChaoMung();
}
private function guiEmailChaoMung() {
// Logic gửi email chào mừng khách hàng mới
echo "Email chào mừng đã được gửi đến {$this->email}";
}
}
// So sánh cách sử dụng
$khachCu = new KhachHangCu();
$khachCu->khoiTao("Nguyen Van A", "a@gmail.com"); // Dễ quên bước này!
$khachMoi = new KhachHang("Tran Thi B", "b@gmail.com"); // Tự động khởi tạo!
?>
Với hàm __construct, bạn đảm bảo rằng mọi đối tượng được tạo ra đều trải qua quá trình khởi tạo đầy đủ và nhất quán. Điều này đặc biệt quan trọng trong các ứng dụng lớn và phức tạp.
Để tìm hiểu sâu hơn về lập trình hướng đối tượng và tính kế thừa trong PHP, bạn có thể tham khảo bài viết Hàm trong Python: Định nghĩa, Cách khai báo, Sử dụng và Mẹo Tối ưu phù hợp để hiểu khái niệm hàm tổng quát trong lập trình.
So sánh __construct với phương thức có tên trùng với lớp
Trong lịch sử PHP, trước khi có hàm __construct, các developer thường sử dụng phương thức có tên trùng với tên class để làm hàm khởi tạo. Tuy nhiên, cách này có nhiều hạn chế và không được khuyến khích sử dụng trong PHP hiện đại.

<?php
// Cách cũ (không khuyến khích)
class XeCu {
public $hang;
public $mau;
// Hàm khởi tạo trùng tên với class
public function XeCu($hang, $mau) {
$this->hang = $hang;
$this->mau = $mau;
}
}
// Cách mới (khuyến khích)
class Xe {
public $hang;
public $mau;
public function __construct($hang, $mau) {
$this->hang = $hang;
$this->mau = $mau;
}
}
?>
Tại sao nên sử dụng __construct thay vì phương thức trùng tên? Có một số lý do quan trọng:
- Tính tương thích cao hơn: __construct được hỗ trợ trong tất cả phiên bản PHP hiện đại và là chuẩn quốc tế.
- Dễ bảo trì: Khi đổi tên class, bạn không cần phải đổi tên hàm khởi tạo.
- Rõ ràng và dễ hiểu: Mọi developer PHP đều biết __construct là hàm khởi tạo, không cần đoán hay tra cứu.
- Hỗ trợ kế thừa tốt hơn: Trong kế thừa, việc gọi parent::__construct() rõ ràng hơn nhiều so với gọi tên class cha.
Gọi hàm __construct của lớp cha trong lớp con
Khi làm việc với kế thừa trong PHP, một trong những kỹ thuật quan trọng nhất là biết cách gọi hàm __construct của lớp cha từ lớp con. Điều này đảm bảo rằng tất cả logic khởi tạo từ lớp cha được thực hiện đúng cách.

<?php
class DongVat {
protected $ten;
protected $tuoi;
protected $loai;
public function __construct($ten, $tuoi, $loai) {
$this->ten = $ten;
$this->tuoi = $tuoi;
$this->loai = $loai;
echo "Động vật {$ten} đã được tạo.\n";
}
public function hienThiThongTin() {
return "Tên: {$this->ten}, Tuổi: {$this->tuoi}, Loại: {$this->loai}";
}
}
class Cho extends DongVat {
private $giong;
private $mucDoThanThien;
public function __construct($ten, $tuoi, $giong, $mucDoThanThien) {
// Gọi hàm __construct của lớp cha
parent::__construct($ten, $tuoi, "Chó");
// Khởi tạo thuộc tính riêng của lớp con
$this->giong = $giong;
$this->mucDoThanThien = $mucDoThanThien;
echo "Chó giống {$giong} đã được khởi tạo hoàn tất.\n";
}
public function hienThiThongTinChiTiet() {
$thongTinCoBan = $this->hienThiThongTin();
return $thongTinCoBan . ", Giống: {$this->giong}, Mức độ thân thiện: {$this->mucDoThanThien}/10";
}
public function sua() {
return "Gâu gâu! {$this->ten} đang sủa.";
}
}
// Sử dụng
$choCorgi = new Cho("Buddy", 3, "Corgi", 9);
echo $choCorgi->hienThiThongTinChiTiet();
echo $choCorgi->sua();
?>
Tác động của kế thừa đến hàm khởi tạo
Khi một class con kế thừa từ class cha mà không định nghĩa hàm __construct riêng, nó sẽ tự động sử dụng hàm __construct của class cha. Tuy nhiên, nếu class con có hàm __construct riêng, nó sẽ “ghi đè” (override) hàm của class cha.

<?php
class PhuongTien {
protected $soChoNgoi;
protected $mauSac;
public function __construct($soChoNgoi, $mauSac) {
$this->soChoNgoi = $soChoNgoi;
$this->mauSac = $mauSac;
echo "Phương tiện với {$soChoNgoi} chỗ ngồi màu {$mauSac} đã được tạo.\n";
}
}
// Class con không có __construct riêng
class XeDap extends PhuongTien {
private $loaiXeDap;
public function datLoaiXeDap($loai) {
$this->loaiXeDap = $loai;
}
}
// Class con có __construct riêng
class OTo extends PhuongTien {
private $dongCo;
private $nhienLieu;
public function __construct($soChoNgoi, $mauSac, $dongCo, $nhienLieu) {
parent::__construct($soChoNgoi, $mauSac);
$this->dongCo = $dongCo;
$this->nhienLieu = $nhienLieu;
echo "Ô tô với động cơ {$dongCo} sử dụng {$nhienLieu} đã sẵn sàng.\n";
}
}
// Test
$xeDap = new XeDap(1, "đỏ"); // Sử dụng __construct của class cha
$oto = new OTo(4, "trắng", "2.0L", "xăng"); // Sử dụng __construct riêng
?>
Lưu ý quan trọng khi override hàm __construct:
- Luôn gọi parent::__construct(): Để đảm bảo logic khởi tạo của lớp cha được thực hiện.
- Thứ tự gọi hàm: Thường nên gọi parent::__construct() trước, sau đó mới thực hiện logic khởi tạo riêng.
- Tham số phù hợp: Đảm bảo truyền đủ và đúng tham số khi gọi hàm __construct của lớp cha.
Chi tiết hơn về kế thừa trong lập trình hướng đối tượng bạn có thể tìm hiểu thông qua bài viết về Vòng lặp trong Python, giúp mở rộng kiến thức về các kỹ thuật lặp và xử lý dữ liệu phức tạp trong lập trình.
Lỗi không gọi được hàm __construct
Một trong những lỗi phổ biến nhất mà các developer mới thường gặp phải là hàm __construct không được gọi tự động. Nguyên nhân chủ yếu thường do viết sai cú pháp hoặc tên hàm.

<?php
// SAI - Thiếu dấu gạch dưới
class LopHocSai1 {
public function _construct($tenLop) {
echo "Lớp học được tạo"; // Không được gọi!
}
}
// SAI - Chỉ có một dấu gạch dưới
class LopHocSai2 {
public function _construct($tenLop) {
echo "Lớp học được tạo"; // Không được gọi!
}
}
// SAI - Viết hoa sai
class LopHocSai3 {
public function __Construct($tenLop) {
echo "Lớp học được tạo"; // Có thể không được gọi!
}
}
// ĐÚNG
class LopHoc {
private $tenLop;
private $siSo;
public function __construct($tenLop, $siSo = 30) {
$this->tenLop = $tenLop;
$this->siSo = $siSo;
echo "Lớp {$tenLop} với {$siSo} học sinh đã được khởi tạo.\n";
}
}
// Test
$lop1 = new LopHocSai1("10A1"); // Không có thông báo nào
$lop2 = new LopHoc("10A2"); // "Lớp 10A2 với 30 học sinh đã được khởi tạo."
?>
Cách khắc phục:
- Luôn viết chính xác:
__construct
(hai dấu gạch dưới, chữ thường)
- Sử dụng IDE có tính năng gợi ý để tránh lỗi đánh máy
- Kiểm tra kỹ trước khi chạy code
Lỗi truyền tham số sai kiểu hoặc thiếu tham số
PHP là ngôn ngữ có tính linh hoạt cao về kiểu dữ liệu, nhưng điều này cũng dễ dẫn đến lỗi khi truyền tham số vào hàm __construct.
<?php
class TaiKhoanNganHang {
private $soTaiKhoan;
private $soDu;
private $loaiTaiKhoan;
public function __construct($soTaiKhoan, $soDu, $loaiTaiKhoan = "Tiết kiệm") {
// Kiểm tra kiểu dữ liệu
if (!is_string($soTaiKhoan) || empty($soTaiKhoan)) {
throw new InvalidArgumentException("Số tài khoản phải là chuỗi không rỗng");
}
if (!is_numeric($soDu) || $soDu < 0) {
throw new InvalidArgumentException("Số dư phải là số không âm");
}
if (!in_array($loaiTaiKhoan, ["Tiết kiệm", "Vãng lai", "Định kỳ"])) {
throw new InvalidArgumentException("Loại tài khoản không hợp lệ");
}
$this->soTaiKhoan = $soTaiKhoan;
$this->soDu = $soDu;
$this->loaiTaiKhoan = $loaiTaiKhoan;
}
public function hienThiThongTin() {
return "Tài khoản: {$this->soTaiKhoan}, Số dư: " . number_format($this->soDu) . " VND, Loại: {$this->loaiTaiKhoan}";
}
}
// Ví dụ sử dụng đúng và sai
try {
$tk1 = new TaiKhoanNganHang("123456789", 1000000); // ĐÚNG
echo $tk1->hienThiThongTin() . "\n";
$tk2 = new TaiKhoanNganHang("", 500000); // SAI - số tài khoản rỗng
} catch (InvalidArgumentException $e) {
echo "Lỗi: " . $e->getMessage() . "\n";
}
try {
$tk3 = new TaiKhoanNganHang("987654321", -100000); // SAI - số dư âm
} catch (InvalidArgumentException $e) {
echo "Lỗi: " . $e->getMessage() . "\n";
}
?>

Các best practices để tránh lỗi tham số:
- Kiểm tra kiểu dữ liệu: Sử dụng
is_string()
, is_numeric()
, is_array()
để kiểm tra
- Sử dụng type hints: Từ PHP 7.0 trở lên, bạn có thể khai báo kiểu dữ liệu cho tham số
- Xử lý exception: Ném exception với thông báo rõ ràng khi dữ liệu không hợp lệ
- Giá trị mặc định: Đặt giá trị mặc định hợp lý cho các tham số không bắt buộc
Sau nhiều năm kinh nghiệm làm việc với PHP, tôi đã tổng hợp những nguyên tắc quan trọng nhất để sử dụng hàm __construct một cách hiệu quả và chuyên nghiệp. Những principles này sẽ giúp code của bạn trở nên clean, maintainable và robust hơn.

1. Luôn sử dụng __construct thay vì phương thức trùng tên lớp
Đây là nguyên tắc số một và không có ngoại lệ. Hàm __construct không chỉ là chuẩn mực hiện đại mà còn đảm bảo tính tương thích và dễ bảo trì.
<?php
// ĐÚNG - Sử dụng __construct
class QuanLyDuAn {
private $tenDuAn;
private $ngayBatDau;
public function __construct($tenDuAn, $ngayBatDau) {
$this->tenDuAn = $tenDuAn;
$this->ngayBatDau = $ngayBatDau;
}
}
// SAI - Không sử dụng phương thức trùng tên (deprecated)
class QuanLyDuAnCu {
private $tenDuAn;
public function QuanLyDuAnCu($tenDuAn) { // Không khuyến khích
$this->tenDuAn = $tenDuAn;
}
}
?>
2. Truyền tham số hợp lý và kiểm tra đầu vào kỹ càng
Hàm __construct là “cửa ngõ” đầu tiên của đối tượng, vì vậy việc kiểm tra dữ liệu đầu vào tại đây rất quan trọng.
<?php
class NhanVien {
private $hoTen;
private $email;
private $luong;
private $phongBan;
public function __construct($hoTen, $email, $luong, $phongBan = null) {
$this->setHoTen($hoTen);
$this->setEmail($email);
$this->setLuong($luong);
$this->phongBan = $phongBan;
}
private function setHoTen($hoTen) {
if (empty($hoTen) || !is_string($hoTen)) {
throw new InvalidArgumentException("Họ tên không được rỗng và phải là chuỗi");
}
if (strlen($hoTen) < 2 || strlen($hoTen) > 50) {
throw new InvalidArgumentException("Họ tên phải từ 2-50 ký tự");
}
$this->hoTen = trim($hoTen);
}
private function setEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Email không hợp lệ");
}
$this->email = strtolower(trim($email));
}
private function setLuong($luong) {
if (!is_numeric($luong) || $luong < 0) {
throw new InvalidArgumentException("Lương phải là số không âm");
}
$this->luong = (float) $luong;
}
}
?>
3. Gọi hàm __construct của lớp cha trong kế thừa
Đây là một trong những lỗi phổ biến nhất – quên gọi parent::__construct() trong class con.
<?php
class NhanVienQuanLy extends NhanVien {
private $capBac;
private $danhSachNhanVienQuanLy;
public function __construct($hoTen, $email, $luong, $capBac, $phongBan = "Quản lý") {
// QUAN TRỌNG: Luôn gọi constructor của lớp cha
parent::__construct($hoTen, $email, $luong, $phongBan);
// Sau đó mới khởi tạo thuộc tính riêng
$this->capBac = $capBac;
$this->danhSachNhanVienQuanLy = [];
}
public function themNhanVienQuanLy(NhanVien $nhanVien) {
$this->danhSachNhanVienQuanLy[] = $nhanVien;
}
}
?>
4. Tránh logic phức tạp trong hàm __construct
Hàm __construct nên chỉ tập trung vào việc khởi tạo thuộc tính. Logic nghiệp vụ phức tạp nên được tách ra thành các method riêng biệt.

<?php
// SAI - Quá nhiều logic trong __construct
class KetNoiDatabase {
private $connection;
public function __construct($host, $database, $username, $password) {
// Quá nhiều logic ở đây!
$dsn = "mysql:host=$host;dbname=$database;charset=utf8mb4";
try {
$this->connection = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
// Kiểm tra kết nối
$this->connection->query("SELECT 1");
echo "Kết nối database thành công!";
} catch (PDOException $e) {
throw new Exception("Lỗi kết nối database: " . $e->getMessage());
}
}
}
// ĐÚNG - Tách logic ra method riêng
class KetNoiDatabaseClean {
private $host;
private $database;
private $username;
private $password;
private $connection;
public function __construct($host, $database, $username, $password) {
$this->host = $host;
$this->database = $database;
$this->username = $username;
$this->password = $password;
}
public function ketNoi() {
if ($this->connection === null) {
$this->taoKetNoi();
}
return $this->connection;
}
private function taoKetNoi() {
$dsn = "mysql:host={$this->host};dbname={$this->database};charset=utf8mb4";
try {
$this->connection = new PDO($dsn, $this->username, $this->password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
throw new Exception("Lỗi kết nối database: " . $e->getMessage());
}
}
}
?>
5. Đặt tên biến và tham số rõ ràng, dễ hiểu
Tên biến và tham số nên thể hiện rõ mục đích sử dụng, giúp code self-documenting.
<?php
// SAI - Tên biến không rõ nghĩa
class SP {
private $t;
private $g;
public function __construct($a, $b) {
$this->t = $a;
$this->g = $b;
}
}
// ĐÚNG - Tên biến và tham số rõ nghĩa
class SanPhamDienTu {
private $tenSanPham;
private $giaBan;
private $thuongHieu;
private $baoHanh;
public function __construct(
$tenSanPham,
$giaBan,
$thuongHieu,
$thoiGianBaoHanh = 12
) {
$this->tenSanPham = $tenSanPham;
$this->giaBan = $giaBan;
$this->thuongHieu = $thuongHieu;
$this->baoHanh = $thoiGianBaoHanh;
}
}
?>
Qua hành trình tìm hiểu chi tiết về hàm __construct trong PHP, chúng ta đã cùng nhau khám phá một trong những khái niệm fundamental nhất trong lập trình hướng đối tượng. Từ những bước đầu tiên với cú pháp cơ bản cho đến việc áp dụng các best practices phức tạp, hàm __construct thực sự là “chìa khóa vàng” giúp bạn tạo ra những đối tượng mạnh mẽ và đáng tin cậy.

Hãy nhớ những điểm quan trọng nhất: hàm __construct tự động được gọi khi tạo đối tượng, giúp khởi tạo thuộc tính một cách nhất quán và hiệu quả. Việc sử dụng __construct thay vì phương thức trùng tên với class không chỉ là xu hướng hiện đại mà còn đảm bảo code của bạn có tính tương thích cao. Trong kế thừa, đừng bao giờ quên gọi parent::__construct() để đảm bảo logic khởi tạo từ lớp cha được thực hiện đầy đủ.
Những lỗi phổ biến như viết sai tên hàm, truyền tham số sai kiểu, hay quá tải logic trong constructor đều có thể tránh được nếu bạn áp dụng đúng các nguyên tắc mà chúng ta đã thảo luận. Hãy luôn kiểm tra dữ liệu đầu vào nghiêm ngặt, sử dụng tên biến có ý nghĩa, và giữ cho hàm __construct đơn giản, tập trung vào mục đích chính là khởi tạo.
Bây giờ là lúc bạn thực hành! Hãy áp dụng những kiến thức này vào các dự án PHP của mình. Bắt đầu với những ví dụ đơn giản, sau đó dần dần nâng cao độ phức tạp. Đừng ngại thử nghiệm với kế thừa và xử lý exception – đó là cách tốt nhất để làm chủ hàm __construct.
Tôi hy vọng bài viết này đã giúp bạn hiểu sâu hơn về hàm __construct và cách sử dụng nó một cách chuyên nghiệp. Nếu bạn có bất kỳ thắc mắc nào hoặc muốn chia sẻ kinh nghiệm của mình, đừng ngần ngại để lại comment. Và đừng quên theo dõi blog để cập nhật thêm nhiều kỹ thuật lập trình PHP hữu ích khác nhé!

Chia sẻ Tài liệu học PHP