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
?>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
?>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());
?>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}{$this->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
- Bài 1: Tạo class
Collectionvới magic methods: __get (lấy item), __set (thêm item), __isset (kiểm tra), __unset (xóa), __toString (JSON), __invoke (filter), __count (đếm). - Bài 2: Tạo class
Requestvới __get để truy cập $_GET/$_POST/$_COOKIE, __call để tạo dynamic methods: hasPost(), getQuery(), getCookie(). - Bài 3: Tạo class
Loggervớ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
?>