Magic Methods

Sau khi nắm vững Static Methods, bước tiếp theo là học Magic Methods – các phương thức đặc biệt bắt đầu bằng __ (2 dấu gạch dưới) được PHP tự động gọi trong các tình huống cụ thể. Magic Methods giúp tạo ra các class linh hoạt, dễ sử dụng và có thể tùy chỉnh hành vi của object.

1. Magic Methods là gì?

Magic Methods (Phương thức ma thuật) là các methods đặc biệt được PHP tự động gọi khi có sự kiện xảy ra với object. Chúng luôn bắt đầu bằng __ (double underscore).

1.1. Đặc điểm Magic Methods

  • ✅ Bắt đầu với __ (2 dấu gạch dưới)
  • ✅ Được PHP tự động gọi, không cần gọi thủ công
  • ✅ Cho phép can thiệp vào các thao tác với object
  • ✅ Tạo API linh hoạt và dễ sử dụng

1.2. Danh sách Magic Methods

<?php
// 15+ Magic Methods trong PHP

__construct()     // Tạo object
__destruct()      // Hủy object
__get()          // Đọc property không tồn tại
__set()          // Ghi property không tồn tại
__isset()        // Kiểm tra property với isset()
__unset()        // Xóa property với unset()
__call()         // Gọi method không tồn tại
__callStatic()   // Gọi static method không tồn tại
__toString()     // Chuyển object thành string
__invoke()       // Gọi object như function
__clone()        // Clone object
__sleep()        // Serialize object
__wakeup()       // Unserialize object
__serialize()    // Serialize (PHP 7.4+)
__unserialize()  // Unserialize (PHP 7.4+)
__set_state()    // Export object với var_export()
__debugInfo()    // Hiển thị thông tin debug
?>
MAGIC METHODS - PHƯƠNG THỨC ĐẶC BIỆT OBJECT Tự động gọi magic methods LIFECYCLE __construct() __destruct() __clone() __sleep() __wakeup() CONVERSION __toString() __invoke() __debugInfo() PROPERTY ACCESS __get($name) __set($n, $v) __isset($name) __unset($name) METHOD CALL __call($n, $args) __callStatic($n, $a) USE CASES 🔧 ORM Models 🎯 Dynamic Properties 🚀 Fluent APIs 🔍 Query Builders 📦 Collections 🎨 HTML Builders 🛡️ Validation 🔐 Debug Info 💡 Bắt đầu với __ (double underscore) - Tự động gọi bởi PHP
Hình 1: Tổng quan Magic Methods

2. __construct() và __destruct()

Đã học chi tiết ở Bài 14: Constructor và Destructor.

<?php
class User {
  private $name;
  
  // __construct - Tự động gọi khi new
  public function __construct($name) {
    $this->name = $name;
    echo "User $name created
"; } // __destruct - Tự động gọi khi object bị hủy public function __destruct() { echo "User {$this->name} destroyed
"; } } $user = new User("An"); // User An created // Script kết thúc → User An destroyed ?>

3. __get() và __set() – Property Overloading

Tự động gọi khi truy cập property không tồn tại hoặc private/protected.

3.1. __get($name) – Đọc property

<?php
class DynamicProperties {
  private $data = [];
  
  // Tự động gọi khi đọc property không tồn tại
  public function __get($name) {
    echo "Getting property: $name
"; if (array_key_exists($name, $this->data)) { return $this->data[$name]; } return null; } } $obj = new DynamicProperties(); echo $obj->name; // Getting property: name // null ?>

3.2. __set($name, $value) – Ghi property

<?php
class DynamicProperties {
  private $data = [];
  
  public function __get($name) {
    return $this->data[$name] ?? null;
  }
  
  // Tự động gọi khi ghi property không tồn tại
  public function __set($name, $value) {
    echo "Setting $name = $value
"; $this->data[$name] = $value; } } $obj = new DynamicProperties(); $obj->name = "An"; // Setting name = An $obj->age = 25; // Setting age = 25 echo $obj->name; // An echo $obj->age; // 25 ?>

3.3. Ví dụ: Validation trong __set()

<?php
class User {
  private $data = [];
  
  public function __set($name, $value) {
    // Validation
    if ($name === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
      throw new Exception("Invalid email format");
    }
    
    if ($name === 'age' && ($value < 0 || $value > 150)) {
      throw new Exception("Invalid age");
    }
    
    $this->data[$name] = $value;
  }
  
  public function __get($name) {
    return $this->data[$name] ?? null;
  }
}

$user = new User();
$user->email = "user@email.com";  // OK
$user->age = 25;                   // OK

// $user->email = "invalid";       // Exception: Invalid email format
// $user->age = 200;                // Exception: Invalid age
?>
__GET() VÀ __SET() - PROPERTY OVERLOADING __GET($name) Khi nào gọi: • Đọc property không tồn tại • Đọc private/protected property Code: public function __get($name) { if (isset($this->data[$name])) { return $this->data[$name]; } return null; } Sử dụng: echo $obj->name; __SET($name, $value) Khi nào gọi: • Ghi property không tồn tại • Ghi private/protected property Code: public function __set($n, $v) { // Validation if ($n === 'email' && !filter_var($v, ...)) { throw Exception(); } Sử dụng: $obj->name = "An"; 💡 Property Overloading - Xử lý properties không tồn tại
Hình 2: __get() và __set() - Property Overloading

4. __isset() và __unset()

4.1. __isset($name) – Kiểm tra property

<?php
class DynamicProperties {
  private $data = ['name' => 'An', 'age' => 25];
  
  // Tự động gọi khi dùng isset() hoặc empty()
  public function __isset($name) {
    echo "Checking if $name is set
"; return isset($this->data[$name]); } public function __get($name) { return $this->data[$name] ?? null; } } $obj = new DynamicProperties(); if (isset($obj->name)) { // Checking if name is set echo "Name exists
"; } if (empty($obj->address)) { // Checking if address is set echo "Address not set
"; } ?>

4.2. __unset($name) – Xóa property

<?php
class DynamicProperties {
  private $data = ['name' => 'An', 'age' => 25];
  
  public function __get($name) {
    return $this->data[$name] ?? null;
  }
  
  public function __set($name, $value) {
    $this->data[$name] = $value;
  }
  
  public function __isset($name) {
    return isset($this->data[$name]);
  }
  
  // Tự động gọi khi dùng unset()
  public function __unset($name) {
    echo "Unsetting $name
"; unset($this->data[$name]); } } $obj = new DynamicProperties(); echo $obj->name; // An unset($obj->name); // Unsetting name echo $obj->name; // null (đã xóa) ?>

5. __call() và __callStatic() – Method Overloading

5.1. __call($name, $arguments) – Method không tồn tại

<?php
class QueryBuilder {
  private $table;
  private $where = [];
  
  public function __construct($table) {
    $this->table = $table;
  }
  
  // Tự động gọi khi gọi method không tồn tại
  public function __call($name, $arguments) {
    // Tạo dynamic where methods: whereEmail(), whereAge()...
    if (strpos($name, 'where') === 0) {
      $field = strtolower(substr($name, 5));  // whereEmail → email
      $this->where[] = "$field = '{$arguments[0]}'";
      return $this;
    }
    
    throw new Exception("Method $name does not exist");
  }
  
  public function get() {
    $sql = "SELECT * FROM {$this->table}";
    
    if (!empty($this->where)) {
      $sql .= " WHERE " . implode(' AND ', $this->where);
    }
    
    return $sql;
  }
}

$query = new QueryBuilder('users');
$sql = $query->whereEmail('user@email.com')
             ->whereAge(25)
             ->whereStatus('active')
             ->get();

echo $sql;
// SELECT * FROM users WHERE email = 'user@email.com' AND age = '25' AND status = 'active'
?>

5.2. __callStatic($name, $arguments) – Static method không tồn tại

<?php
class Route {
  private static $routes = [];
  
  // Tự động gọi khi gọi static method không tồn tại
  public static function __callStatic($method, $arguments) {
    // Tạo dynamic route methods: Route::get(), Route::post()...
    $method = strtoupper($method);  // get → GET
    $path = $arguments[0];
    $handler = $arguments[1];
    
    self::$routes[$method][$path] = $handler;
    
    echo "Registered: $method $path
"; } public static function getRoutes() { return self::$routes; } } // Dynamic methods không cần khai báo Route::get('/home', 'HomeController@index'); Route::post('/login', 'AuthController@login'); Route::put('/user/1', 'UserController@update'); Route::delete('/user/1', 'UserController@delete'); print_r(Route::getRoutes()); ?>
__CALL(), __CALLSTATIC() VÀ __INVOKE() __CALL() Gọi khi: Method không tồn tại Code: function __call($n, $a) { if (str_starts_with( $n, 'where')) { $field = substr($n, 5); return $this ->where($field, $a[0]); } } Ví dụ: $obj->whereEmail('x'); $obj->whereAge(25); __CALLSTATIC() Gọi khi: Static method không tồn tại Code: static function __callStatic($n, $a) { $method = strtoupper($n); $path = $a[0]; $handler = $a[1]; self::$routes[$method] } Ví dụ: Route::get('/', $fn); Route::post('/api', $fn); __INVOKE() Gọi khi: Object được gọi như function Code: function __invoke($n) { return $n * $this->factor; } Ví dụ: $double = new Multi(2); echo $double(5); // 10 // Gọi như function 💡 Method Overloading - Dynamic & Callable Objects
Hình 3: __call(), __callStatic() và __invoke()

6. __toString() – Chuyển object thành string

Tự động gọi khi object được sử dụng như string.

<?php
class User {
  private $name;
  private $email;
  
  public function __construct($name, $email) {
    $this->name = $name;
    $this->email = $email;
  }
  
  // Tự động gọi khi echo, print, string concat
  public function __toString() {
    return "{$this->name} ({$this->email})";
  }
}

$user = new User("Nguyễn Văn An", "an@email.com");

echo $user;  // Nguyễn Văn An (an@email.com)

$message = "User: " . $user;
echo $message;  // User: Nguyễn Văn An (an@email.com)
?>

6.1. Ví dụ: HTML rendering

<?php
class HTML {
  private $tag;
  private $attributes = [];
  private $content = '';
  
  public function __construct($tag) {
    $this->tag = $tag;
  }
  
  public function attr($name, $value) {
    $this->attributes[$name] = $value;
    return $this;
  }
  
  public function content($content) {
    $this->content = $content;
    return $this;
  }
  
  public function __toString() {
    $attrs = '';
    foreach ($this->attributes as $name => $value) {
      $attrs .= " $name=\"$value\"";
    }
    
    return "<{$this->tag}{$attrs}>{$this->content}tag}>";
  }
}

$link = (new HTML('a'))
  ->attr('href', 'https://example.com')
  ->attr('class', 'btn btn-primary')
  ->content('Click Here');

echo $link;
// Click Here

$div = (new HTML('div'))
  ->attr('id', 'container')
  ->content('Hello World');

echo $div;
// 
Hello World
?>

7. __invoke() – Gọi object như function

Cho phép gọi object như một function.

<?php
class Multiplier {
  private $factor;
  
  public function __construct($factor) {
    $this->factor = $factor;
  }
  
  // Tự động gọi khi object được gọi như function
  public function __invoke($number) {
    return $number * $this->factor;
  }
}

$double = new Multiplier(2);
$triple = new Multiplier(3);

echo $double(5);   // 10 (5 * 2)
echo $triple(5);   // 15 (5 * 3)
echo $double(10);  // 20 (10 * 2)

// Dùng với array_map
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map($double, $numbers);
print_r($doubled);  // [2, 4, 6, 8, 10]
?>

7.1. Ví dụ: Callable middleware

<?php
class AuthMiddleware {
  private $user;
  
  public function __construct($user) {
    $this->user = $user;
  }
  
  public function __invoke($request) {
    if (!$this->user) {
      return "Unauthorized - Please login";
    }
    
    return "Processing request for user: {$this->user}";
  }
}

$middleware = new AuthMiddleware("john_doe");

// Gọi như function
echo $middleware(['uri' => '/dashboard']);
// Processing request for user: john_doe

// Không có user
$noAuth = new AuthMiddleware(null);
echo $noAuth(['uri' => '/dashboard']);
// Unauthorized - Please login
?>

8. __clone() – Clone object

Tự động gọi khi clone object với từ khóa clone.

<?php
class Address {
  public $street;
  public $city;
  
  public function __construct($street, $city) {
    $this->street = $street;
    $this->city = $city;
  }
}

class Person {
  public $name;
  public $address;
  
  public function __construct($name, $address) {
    $this->name = $name;
    $this->address = $address;
  }
  
  // Tự động gọi khi clone
  public function __clone() {
    // Deep clone: clone cả nested objects
    $this->address = clone $this->address;
    
    echo "Cloned {$this->name}
"; } } $address = new Address("123 Main St", "Hanoi"); $person1 = new Person("An", $address); $person2 = clone $person1; // Cloned An // Thay đổi person2 không ảnh hưởng person1 $person2->name = "Bình"; $person2->address->city = "HCMC"; echo $person1->name . " - " . $person1->address->city; // An - Hanoi echo $person2->name . " - " . $person2->address->city; // Bình - HCMC ?>

9. __sleep() và __wakeup() – Serialization

9.1. __sleep() – Trước khi serialize

<?php
class Connection {
  private $server;
  private $username;
  private $password;
  private $db;
  private $conn;  // Resource - không thể serialize
  
  public function __construct($server, $username, $password, $db) {
    $this->server = $server;
    $this->username = $username;
    $this->password = $password;
    $this->db = $db;
    $this->connect();
  }
  
  private function connect() {
    $this->conn = new PDO("mysql:host={$this->server};dbname={$this->db}", 
                          $this->username, $this->password);
    echo "Connected to database
"; } // Tự động gọi trước khi serialize public function __sleep() { echo "Preparing to serialize
"; // Trả về array các properties muốn serialize // Bỏ $conn vì nó là resource return ['server', 'username', 'password', 'db']; } // Tự động gọi sau khi unserialize public function __wakeup() { echo "Waking up from serialization
"; // Kết nối lại database $this->connect(); } } $conn = new Connection("localhost", "root", "password", "mydb"); // Connected to database $serialized = serialize($conn); // Preparing to serialize $restored = unserialize($serialized); // Waking up from serialization // Connected to database ?>

10. __debugInfo() – Thông tin debug

Tùy chỉnh thông tin hiển thị khi dùng var_dump().

<?php
class User {
  private $name;
  private $email;
  private $password;  // Sensitive
  private $api_token; // Sensitive
  
  public function __construct($name, $email, $password, $token) {
    $this->name = $name;
    $this->email = $email;
    $this->password = $password;
    $this->api_token = $token;
  }
  
  // Tự động gọi khi var_dump()
  public function __debugInfo() {
    return [
      'name' => $this->name,
      'email' => $this->email,
      'password' => '******',        // Ẩn password
      'api_token' => '***hidden***'  // Ẩn token
    ];
  }
}

$user = new User("An", "an@email.com", "secret123", "abc-xyz-token");

var_dump($user);
// object(User)#1 (4) {
//   ["name"]=> string(2) "An"
//   ["email"]=> string(13) "an@email.com"
//   ["password"]=> string(6) "******"
//   ["api_token"]=> string(12) "***hidden***"
// }
?>

11. Ví dụ thực tế tổng hợp

11.1. ORM Model với Magic Methods

<?php
class Model {
  protected $table;
  protected $attributes = [];
  protected static $conn;
  
  public static function setConnection($conn) {
    self::$conn = $conn;
  }
  
  // __get - Đọc attribute
  public function __get($name) {
    return $this->attributes[$name] ?? null;
  }
  
  // __set - Ghi attribute
  public function __set($name, $value) {
    $this->attributes[$name] = $value;
  }
  
  // __isset - Kiểm tra attribute
  public function __isset($name) {
    return isset($this->attributes[$name]);
  }
  
  // __call - Dynamic finder methods
  public function __call($method, $arguments) {
    if (strpos($method, 'findBy') === 0) {
      $field = strtolower(substr($method, 6));  // findByEmail → email
      return $this->where($field, $arguments[0])->get();
    }
    
    throw new Exception("Method $method does not exist");
  }
  
  // __callStatic - Static finder
  public static function __callStatic($method, $arguments) {
    $instance = new static();
    return call_user_func_array([$instance, $method], $arguments);
  }
  
  // __toString - JSON representation
  public function __toString() {
    return json_encode($this->attributes, JSON_PRETTY_PRINT);
  }
  
  private function where($field, $value) {
    $stmt = self::$conn->prepare("SELECT * FROM {$this->table} WHERE $field = ?");
    $stmt->execute([$value]);
    $this->attributes = $stmt->fetch(PDO::FETCH_ASSOC);
    return $this;
  }
  
  private function get() {
    return $this->attributes;
  }
  
  public function save() {
    if (isset($this->attributes['id'])) {
      // Update
      $fields = [];
      $values = [];
      
      foreach ($this->attributes as $key => $value) {
        if ($key !== 'id') {
          $fields[] = "$key = ?";
          $values[] = $value;
        }
      }
      
      $values[] = $this->attributes['id'];
      $sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . " WHERE id = ?";
      
      $stmt = self::$conn->prepare($sql);
      $stmt->execute($values);
    } else {
      // Insert
      $fields = implode(', ', array_keys($this->attributes));
      $placeholders = implode(', ', array_fill(0, count($this->attributes), '?'));
      
      $sql = "INSERT INTO {$this->table} ($fields) VALUES ($placeholders)";
      $stmt = self::$conn->prepare($sql);
      $stmt->execute(array_values($this->attributes));
      
      $this->attributes['id'] = self::$conn->lastInsertId();
    }
  }
}

class User extends Model {
  protected $table = 'users';
}

// Setup
$pdo = new PDO("mysql:host=localhost;dbname=test", "root", "");
Model::setConnection($pdo);

// Sử dụng Magic Methods
$user = new User();
$user->name = "Nguyễn Văn An";     // __set
$user->email = "an@email.com";     // __set
$user->save();

echo $user->name;                   // __get: Nguyễn Văn An
echo $user;                         // __toString: JSON

// Dynamic finder
$foundUser = User::findByEmail("an@email.com");  // __callStatic + __call
print_r($foundUser);
?>

11.2. Fluent Configuration Builder

<?php
class Config {
  private $data = [];
  
  public function __set($name, $value) {
    $this->data[$name] = $value;
  }
  
  public function __get($name) {
    return $this->data[$name] ?? null;
  }
  
  public function __call($method, $arguments) {
    // Setter: setAppName() → app_name
    if (strpos($method, 'set') === 0) {
      $key = strtolower(preg_replace('/(?data[$key] = $arguments[0];
      return $this;
    }
    
    // Getter: getAppName() → app_name
    if (strpos($method, 'get') === 0) {
      $key = strtolower(preg_replace('/(?data[$key] ?? null;
    }
    
    throw new Exception("Method $method not found");
  }
  
  public function __toString() {
    return json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
  }
  
  public function __debugInfo() {
    return $this->data;
  }
}

$config = new Config();

// Fluent interface với dynamic methods
$config->setAppName("My Application")
       ->setDebugMode(true)
       ->setTimezone("Asia/Ho_Chi_Minh")
       ->setDatabaseHost("localhost")
       ->setDatabaseName("mydb");

echo $config->getAppName();      // My Application
echo $config->getDatabaseHost(); // localhost

echo $config;  // JSON output
?>

12. Best Practices

NÊN:

  • Dùng __toString() cho representation rõ nghĩa
  • Dùng __debugInfo() để ẩn sensitive data
  • Validate trong __set()
  • Deep clone trong __clone() nếu có nested objects
  • Document magic methods rõ ràng

KHÔNG NÊN:

  • Lạm dụng magic methods (khó debug, khó hiểu)
  • Logic phức tạp trong magic methods
  • Quên handle edge cases trong __get()/__set()
  • Tạo method bắt đầu với __ tự ý (trừ magic methods)

Tóm tắt

Qua bài này bạn đã nắm được 15+ Magic Methods:

  • __construct/__destruct: Tạo/hủy object
  • __get/__set: Đọc/ghi property không tồn tại
  • __isset/__unset: Kiểm tra/xóa property
  • __call/__callStatic: Gọi method không tồn tại
  • __toString: Chuyển object → string
  • __invoke: Gọi object như function
  • __clone: Clone object
  • __sleep/__wakeup: Serialize/Unserialize
  • __debugInfo: Tùy chỉnh var_dump()

Khi dùng: ORM, Fluent API, Dynamic methods, Property validation, Debug

Bài tiếp theo, bạn sẽ học về Namespace và Autoloading – tổ chức code theo modules.

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

  1. Bài 1: Tạo class Collection với magic methods: __get (lấy item), __set (thêm item), __isset (kiểm tra), __unset (xóa), __toString (JSON), __invoke (filter), __count (đếm).
  2. Bài 2: Tạo class Request với __get để truy cập $_GET/$_POST/$_COOKIE, __call để tạo dynamic methods: hasPost(), getQuery(), getCookie().
  3. Bài 3: Tạo class Logger với __invoke để log messages, __toString để export logs, __debugInfo để hiển thị stats.

Gợi ý Bài 1:

<?php
class Collection {
  private $items = [];
  
  public function __get($key) {
    return $this->items[$key] ?? null;
  }
  
  public function __set($key, $value) {
    $this->items[$key] = $value;
  }
  
  public function __isset($key) {
    return isset($this->items[$key]);
  }
  
  public function __unset($key) {
    unset($this->items[$key]);
  }
  
  public function __toString() {
    return json_encode($this->items, JSON_PRETTY_PRINT);
  }
  
  public function __invoke($callback) {
    return array_filter($this->items, $callback);
  }
  
  public function count() {
    return count($this->items);
  }
}

$collection = new Collection();
$collection->name = "An";
$collection->age = 25;
$collection->city = "Hanoi";

echo $collection->name;  // An

if (isset($collection->age)) {
  echo "Age exists
"; } unset($collection->city); echo $collection; // JSON // Filter với __invoke $filtered = $collection(function($value) { return is_string($value); }); print_r($filtered); echo $collection->count(); // 2 ?>

Để lại bình luận

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