Constructor và Destructor

Sau khi nắm vững Class và Object, bước tiếp theo là học Constructor và Destructor – hai phương thức đặc biệt trong PHP OOP. Constructor tự động chạy khi tạo object (khởi tạo giá trị ban đầu), còn Destructor tự động chạy khi object bị hủy (giải phóng tài nguyên). Đây là nền tảng quan trọng để viết code OOP chuyên nghiệp.

1. Constructor là gì?

Constructor (Hàm khởi tạo) là phương thức đặc biệt tự động được gọi khi tạo object bằng từ khóa new. Dùng để khởi tạo giá trị ban đầu cho properties.

1.1. Vấn đề khi không có Constructor

<?php
class User {
  public $name;
  public $email;
  public $role;
}

// ❌ Phải gán từng property thủ công
$user = new User();
$user->name = "Nguyễn Văn A";
$user->email = "a@email.com";
$user->role = "admin";

// Lặp lại nhiều lần, dễ quên
?>

1.2. Giải pháp: Dùng Constructor

<?php
class User {
  public $name;
  public $email;
  public $role;
  
  // Constructor - tự động chạy khi new User()
  public function __construct($name, $email, $role = "user") {
    $this->name = $name;
    $this->email = $email;
    $this->role = $role;
  }
}

// ✅ Khởi tạo 1 lần, gọn gàng
$user = new User("Nguyễn Văn A", "a@email.com", "admin");

echo $user->name;   // Nguyễn Văn A
echo $user->role;   // admin

// Tham số mặc định
$user2 = new User("Trần Thị B", "b@email.com");
echo $user2->role;  // user (giá trị mặc định)
?>
VÒNG ĐỜI OBJECT BƯỚC 1 $obj = new Class(); Tạo object BƯỚC 2: CONSTRUCTOR __construct() ✓ Tự động chạy ngay BƯỚC 3: SỬ DỤNG OBJECT $obj->method1(); $obj->method2(); BƯỚC 4: DESTRUCTOR __destruct() ✓ Tự động chạy khi object bị hủy Khi nào hủy? • Script kết thúc • unset($obj) • $obj = null • $obj gán object mới • Hết phạm vi (function return)
Hình 1: Vòng đời Object với Constructor và Destructor

1.3. Quy tắc Constructor

  • ✅ Tên cố định: __construct() (2 dấu gạch dưới)
  • ✅ Tự động chạy khi new ClassName()
  • ✅ Có thể có tham số hoặc không
  • ✅ Mỗi class chỉ có 1 constructor
  • ✅ Không có return (kể cả void)
  • ✅ Có thể có access modifier (public/protected/private)

2. Ví dụ Constructor cơ bản

2.1. Constructor không tham số

<?php
class Counter {
  public $count;
  
  // Constructor khởi tạo giá trị mặc định
  public function __construct() {
    $this->count = 0;
    echo "Counter được khởi tạo với giá trị 0";
  }
  
  public function increment() {
    $this->count++;
  }
}

$counter = new Counter();  // In: Counter được khởi tạo với giá trị 0
$counter->increment();
echo $counter->count;  // 1
?>

2.2. Constructor có tham số

<?php
class Product {
  public $name;
  public $price;
  public $quantity;
  
  public function __construct($name, $price, $quantity = 1) {
    $this->name = $name;
    $this->price = $price;
    $this->quantity = $quantity;
  }
  
  public function getTotal() {
    return $this->price * $this->quantity;
  }
}

$product1 = new Product("Laptop", 15000000, 2);
echo $product1->getTotal();  // 30000000

// Dùng tham số mặc định
$product2 = new Product("Chuột", 200000);
echo $product2->quantity;  // 1
?>

2.3. Constructor với Validation

<?php
class BankAccount {
  private $balance;
  private $accountNumber;
  
  public function __construct($accountNumber, $initialBalance = 0) {
    // Validate số tài khoản
    if (strlen($accountNumber) != 10) {
      throw new Exception("Số tài khoản phải 10 chữ số");
    }
    
    // Validate số dư ban đầu
    if ($initialBalance < 0) {
      throw new Exception("Số dư ban đầu không thể âm");
    }
    
    $this->accountNumber = $accountNumber;
    $this->balance = $initialBalance;
    
    echo "Tài khoản {$this->accountNumber} được tạo với số dư: " . 
         number_format($this->balance) . " VNĐ";
  }
  
  public function getBalance() {
    return $this->balance;
  }
}

try {
  $account = new BankAccount("1234567890", 1000000);
  // In: Tài khoản 1234567890 được tạo với số dư: 1,000,000 VNĐ
  
  $invalid = new BankAccount("123", 500000);  // Lỗi!
} catch (Exception $e) {
  echo "Lỗi: " . $e->getMessage();
}
?>
CONSTRUCTOR VS METHOD THƯỜNG CONSTRUCTOR Tên: __construct() Gọi: ✅ Tự động khi new Số lần: 1 lần duy nhất Return: ❌ Không return Mục đích: Khởi tạo object METHOD THƯỜNG Tên: tùy ý (getData...) Gọi: ❌ Phải gọi thủ công Số lần: Gọi nhiều lần Return: ✅ Có thể return Mục đích: Thực hiện hành động 💡 Constructor = Phương thức đặc biệt tự động chạy
Hình 2: Constructor vs Method thường

3. Constructor trong thực tế

3.1. Class Database với Constructor

<?php
class Database {
  private $host;
  private $dbname;
  private $username;
  private $password;
  public $conn;
  
  // Constructor nhận thông tin kết nối
  public function __construct($host = "localhost", $dbname = "test_db", 
                              $username = "root", $password = "") {
    $this->host = $host;
    $this->dbname = $dbname;
    $this->username = $username;
    $this->password = $password;
    
    // Tự động kết nối khi khởi tạo
    $this->connect();
  }
  
  private function connect() {
    try {
      $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4";
      $this->conn = new PDO($dsn, $this->username, $this->password);
      $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      
      echo "Kết nối database thành công!";
    } catch (PDOException $e) {
      die("Lỗi kết nối: " . $e->getMessage());
    }
  }
  
  public function query($sql) {
    return $this->conn->query($sql);
  }
}

// Sử dụng giá trị mặc định
$db = new Database();

// Hoặc custom
$db2 = new Database("localhost", "my_database", "admin", "pass123");
?>

3.2. Class User với Constructor

<?php
class User {
  private $id;
  private $name;
  private $email;
  private $password;
  private $created_at;
  
  public function __construct($name, $email, $password) {
    $this->name = $name;
    $this->email = $email;
    $this->setPassword($password);
    $this->created_at = date("Y-m-d H:i:s");
    
    // Log
    error_log("User {$this->name} được tạo lúc {$this->created_at}");
  }
  
  private function setPassword($password) {
    // Validate độ dài
    if (strlen($password) < 6) {
      throw new Exception("Mật khẩu phải ít nhất 6 ký tự");
    }
    
    // Hash password
    $this->password = password_hash($password, PASSWORD_DEFAULT);
  }
  
  public function checkPassword($password) {
    return password_verify($password, $this->password);
  }
  
  public function getInfo() {
    return [
      'name' => $this->name,
      'email' => $this->email,
      'created_at' => $this->created_at
    ];
  }
}

try {
  $user = new User("Nguyễn Văn A", "a@email.com", "password123");
  print_r($user->getInfo());
  
} catch (Exception $e) {
  echo "Lỗi: " . $e->getMessage();
}
?>

3.3. Class Logger với Constructor

<?php
class Logger {
  private $logFile;
  private $handle;
  
  public function __construct($filename = "app.log") {
    $this->logFile = __DIR__ . "/logs/" . $filename;
    
    // Tạo thư mục logs nếu chưa có
    $logDir = dirname($this->logFile);
    if (!is_dir($logDir)) {
      mkdir($logDir, 0755, true);
    }
    
    // Mở file để ghi
    $this->handle = fopen($this->logFile, "a");
    
    if (!$this->handle) {
      throw new Exception("Không thể mở file log");
    }
    
    $this->write("Logger khởi tạo");
  }
  
  public function write($message) {
    $timestamp = date("Y-m-d H:i:s");
    $line = "[$timestamp] $message" . PHP_EOL;
    fwrite($this->handle, $line);
  }
  
  public function error($message) {
    $this->write("ERROR: $message");
  }
  
  public function info($message) {
    $this->write("INFO: $message");
  }
}

$logger = new Logger("app.log");
$logger->info("Ứng dụng bắt đầu");
$logger->error("Có lỗi xảy ra");
?>

4. Destructor là gì?

Destructor (Hàm hủy) là phương thức đặc biệt tự động được gọi khi object bị hủy (script kết thúc hoặc dùng unset()). Dùng để giải phóng tài nguyên như đóng file, đóng kết nối database.

<?php
class FileHandler {
  private $file;
  private $handle;
  
  // Constructor - mở file
  public function __construct($filename) {
    $this->file = $filename;
    $this->handle = fopen($filename, "a");
    
    if ($this->handle) {
      echo "File {$this->file} được mở
"; } } public function write($content) { if ($this->handle) { fwrite($this->handle, $content . PHP_EOL); } } // Destructor - đóng file public function __destruct() { if ($this->handle) { fclose($this->handle); echo "File {$this->file} được đóng
"; } } } // Sử dụng $file = new FileHandler("log.txt"); $file->write("Dòng 1"); $file->write("Dòng 2"); // Destructor tự động chạy khi script kết thúc // hoặc khi unset($file) ?>
DESTRUCTOR - DỌN DẸP TÀI NGUYÊN __construct() - MỞ TÀI NGUYÊN ✓ Mở file log.txt ✓ Kết nối MySQL ✓ Kết nối Redis ✓ Start session SỬ DỤNG OBJECT $obj->write("Log message"); $obj->query("SELECT ..."); __destruct() - ĐÓNG TÀI NGUYÊN ✓ Đóng file ✓ Đóng MySQL ✓ Đóng Redis ✓ Save session 💡 Destructor đảm bảo tài nguyên luôn được giải phóng
Hình 3: Destructor tự động dọn dẹp tài nguyên

4.1. Quy tắc Destructor

  • ✅ Tên cố định: __destruct()
  • ✅ Không có tham số
  • ✅ Tự động chạy khi object bị hủy
  • ✅ Dùng để dọn dẹp: đóng file, đóng kết nối, giải phóng bộ nhớ
  • ✅ Mỗi class chỉ có 1 destructor

5. Ví dụ Destructor thực tế

5.1. Đóng kết nối Database

<?php
class Database {
  private $conn;
  
  public function __construct($host, $dbname, $user, $pass) {
    try {
      $this->conn = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
      echo "✓ Kết nối database thành công
"; } catch (PDOException $e) { die("Lỗi: " . $e->getMessage()); } } public function query($sql) { return $this->conn->query($sql); } // Destructor đóng kết nối public function __destruct() { $this->conn = null; echo "✓ Đóng kết nối database
"; } } $db = new Database("localhost", "test_db", "root", ""); $result = $db->query("SELECT * FROM users"); // Destructor tự động chạy ở cuối script ?>

5.2. Session Manager

<?php
class Session {
  public function __construct() {
    if (session_status() == PHP_SESSION_NONE) {
      session_start();
      echo "✓ Session bắt đầu
"; } } public function set($key, $value) { $_SESSION[$key] = $value; } public function get($key, $default = null) { return $_SESSION[$key] ?? $default; } public function __destruct() { // Lưu và đóng session session_write_close(); echo "✓ Session được lưu và đóng
"; } } $session = new Session(); $session->set("user_id", 123); $session->set("username", "admin"); echo $session->get("username"); // admin ?>

5.3. Temporary File Handler

<?php
class TempFile {
  private $filename;
  
  public function __construct() {
    // Tạo file tạm
    $this->filename = tempnam(sys_get_temp_dir(), "tmp_");
    echo "✓ File tạm được tạo: {$this->filename}
"; } public function write($content) { file_put_contents($this->filename, $content); } public function read() { return file_get_contents($this->filename); } // Destructor xóa file tạm public function __destruct() { if (file_exists($this->filename)) { unlink($this->filename); echo "✓ File tạm được xóa: {$this->filename}
"; } } } $temp = new TempFile(); $temp->write("Dữ liệu tạm thời"); echo $temp->read(); // File tự động bị xóa khi script kết thúc ?>

6. Constructor Promotion (PHP 8+)

PHP 8 giới thiệu Constructor Promotion – cú pháp ngắn gọn để khai báo properties và gán giá trị trong constructor.

<?php
// ❌ Cách cũ - dài dòng
class User {
  private $name;
  private $email;
  private $age;
  
  public function __construct($name, $email, $age) {
    $this->name = $name;
    $this->email = $email;
    $this->age = $age;
  }
}

// ✅ Constructor Promotion - ngắn gọn
class User {
  public function __construct(
    private string $name,
    private string $email,
    private int $age
  ) {
    // Properties tự động được khai báo và gán giá trị
  }
  
  public function getInfo() {
    return "{$this->name} - {$this->email} - {$this->age} tuổi";
  }
}

$user = new User("Nguyễn Văn A", "a@email.com", 25);
echo $user->getInfo();
?>

6.1. Kết hợp Promotion với Logic

<?php
class Product {
  public function __construct(
    private string $name,
    private float $price,
    private int $quantity = 1
  ) {
    // Vẫn có thể thêm logic
    if ($price < 0) {
      throw new Exception("Giá không thể âm");
    }
    
    echo "Sản phẩm {$this->name} được tạo";
  }
  
  public function getTotal(): float {
    return $this->price * $this->quantity;
  }
}

$product = new Product("Laptop", 15000000, 2);
echo $product->getTotal();  // 30000000
?>

7. So sánh Constructor vs Method thường

Đặc điểmConstructorMethod thường
Tên__construct()Tùy ý
Gọi tự động✅ Khi new❌ Phải gọi thủ công
Số lần gọi1 lần duy nhấtNhiều lần
Return❌ Không✅ Có thể
Mục đíchKhởi tạo objectThực hiện hành động

8. Best Practices

8.1. Constructor nên làm gì

NÊN:

  • Gán giá trị ban đầu cho properties
  • Validate dữ liệu đầu vào
  • Khởi tạo kết nối (database, file…)
  • Set giá trị mặc định
  • Log khởi tạo object

KHÔNG NÊN:

  • Logic nghiệp vụ phức tạp
  • Query database nhiều (chỉ kết nối thôi)
  • Gọi API bên ngoài
  • Xử lý nặng (làm chậm khởi tạo)

8.2. Destructor nên làm gì

<?php
class ResourceManager {
  private $db;
  private $cache;
  private $logFile;
  
  public function __construct() {
    $this->db = new PDO(...);
    $this->cache = new Redis();
    $this->logFile = fopen("app.log", "a");
  }
  
  public function __destruct() {
    // ✅ Dọn dẹp tài nguyên
    $this->db = null;              // Đóng DB
    $this->cache->close();         // Đóng Redis
    fclose($this->logFile);        // Đóng file
    
    // ✅ Log kết thúc
    error_log("ResourceManager destroyed");
  }
}
?>

Tóm tắt

Qua bài này bạn đã nắm được:

  • Constructor: __construct() – tự động chạy khi new ClassName()
  • Mục đích Constructor: Khởi tạo giá trị ban đầu, validate, kết nối
  • Tham số Constructor: Có thể có tham số + giá trị mặc định
  • Destructor: __destruct() – tự động chạy khi object bị hủy
  • Mục đích Destructor: Đóng file, đóng kết nối, giải phóng tài nguyên
  • Constructor Promotion (PHP 8+): Khai báo properties ngắn gọn
  • Magic Methods: Constructor và Destructor là 2 trong nhiều magic methods

Bài tiếp theo, bạn sẽ học về Inheritance (Tính kế thừa) – tái sử dụng code giữa các class.

🎯 Bài tập thực hành

  1. Bài 1: Tạo class Rectangle với constructor nhận width và height. Validate width, height > 0. Tạo methods: getArea(), getPerimeter().
  2. Bài 2: Tạo class FileLogger với constructor nhận tên file. Mở file trong constructor, đóng file trong destructor. Method write($message) ghi log.
  3. Bài 3: Tạo class ShoppingCart với constructor khởi tạo mảng items rỗng. Methods: addItem($product, $quantity), getTotal(), clear().

Gợi ý Bài 1:

<?php
class Rectangle {
  private $width;
  private $height;
  
  public function __construct($width, $height) {
    if ($width <= 0 || $height <= 0) {
      throw new Exception("Width và height phải > 0");
    }
    
    $this->width = $width;
    $this->height = $height;
  }
  
  public function getArea() {
    return $this->width * $this->height;
  }
  
  public function getPerimeter() {
    return 2 * ($this->width + $this->height);
  }
}

try {
  $rect = new Rectangle(5, 10);
  echo "Diện tích: " . $rect->getArea();         // 50
  echo "Chu vi: " . $rect->getPerimeter();       // 30
  
  $invalid = new Rectangle(-5, 10);  // Exception
} catch (Exception $e) {
  echo "Lỗi: " . $e->getMessage();
}
?>

Gợi ý Bài 2:

<?php
class FileLogger {
  private $filename;
  private $handle;
  
  public function __construct($filename) {
    $this->filename = $filename;
    $this->handle = fopen($filename, "a");
    
    if (!$this->handle) {
      throw new Exception("Không thể mở file");
    }
    
    $this->write("=== Logger started ===");
  }
  
  public function write($message) {
    $timestamp = date("Y-m-d H:i:s");
    fwrite($this->handle, "[$timestamp] $message" . PHP_EOL);
  }
  
  public function __destruct() {
    if ($this->handle) {
      $this->write("=== Logger stopped ===");
      fclose($this->handle);
    }
  }
}

$logger = new FileLogger("app.log");
$logger->write("User đăng nhập");
$logger->write("User đăng xuất");
// Destructor tự động đóng file
?>

Để lại bình luận

Email của bạn sẽ không được hiển thị.