首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 魔術方法完整指南:__get/__set、__call/__callStatic、__toString、__invoke 實戰應用

     編輯:PHP 相關知識

魔術方法總覽

方法觸發時機典型用途
__get($name)讀取不可見/不存在屬性動態屬性代理、資料庫欄位映射
__set($name, $value)寫入不可見/不存在屬性屬性驗證、自動儲存、關聯模型
__call($name, $args)呼叫不可見/不存在實例方法動態方法、服務代理、鏈式呼叫
__callStatic($name, $args)呼叫不可見/不存在靜態方法工廠方法、查詢建構器、門面模式
__toString()物件當作字串使用 (echo/print/字串串接)日誌輸出、API 回應、模板渲染
__invoke($args...)物件當作函數呼叫 $obj()可呼叫物件、策略模式、中介軟體

完整實作範例

<?php
class DynamicEntity {
    private array $data = [];
    private array $relations = [];

    // 屬性讀取代理
    public function __get(string $name): mixed {
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }
        // 關聯懶載入
        if (isset($this->relations[$name])) {
            return $this->relations[$name];
        }
        throw new Exception("Property '$name' not found");
    }

    // 屬性寫入代理
    public function __set(string $name, mixed $value): void {
        // 可加入驗證邏輯
        if ($name === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email');
        }
        $this->data[$name] = $value;
    }

    // 動態方法:如 findByEmail、getPosts 等
    public function __call(string $name, array $args): mixed {
        if (str_starts_with($name, 'findBy')) {
            $field = lcfirst(substr($name, 6)); // findByEmail → email
            return $this->repository->findBy($field, $args[0] ?? null);
        }
        if (str_starts_with($name, 'get')) {
            $relation = lcfirst(substr($name, 3));
            return $this->loadRelation($relation);
        }
        throw new BadMethodCallException("Method $name not found");
    }

    // 靜態工廠:User::create(['name'=>'...'])
    public static function __callStatic(string $name, array $args): mixed {
        if ($name === 'create') {
            return (new static())->fill($args[0] ?? [])->save();
        }
        if ($name === 'find') {
            return (new static())->repository->find($args[0] ?? null);
        }
        throw new BadMethodCallException("Static method $name not found");
    }

    // 物件轉字串:echo $user → "User#123: John Doe"
    public function __toString(): string {
        return sprintf("%s#%d: %s", static::class, $this->data['id'] ?? 0, $this->data['name'] ?? 'Unknown');
    }

    // 物件當函數:$user(['email' => 'new@ex.com']) 更新並儲存
    public function __invoke(array $updates): self {
        foreach ($updates as $k => $v) {
            $this->$k = $v; // 觸發 __set 驗證
        }
        return $this->save();
    }
}

// 使用示範
$user = new DynamicEntity();
$user->name = 'Alice';        // __set
$user->email = 'alice@ex.com'; // __set (驗證)
echo $user->name;             // __get → Alice
echo $user;                   // __toString → DynamicEntity#0: Alice
$user(['name' => 'Alice Updated']); // __invoke
User::create(['name' => 'Bob']);    // __callStatic
?>

實戰應用場景

1. ActiveRecord / ORM 基礎類

  • __get/__set 映射資料表欄位
  • __call 實現 findBy*、get*Relation 動態查詢
  • __callStatic 實現 Model::find()、Model::create()

2. 服務門面

<?php
class ServiceFacade {
    private array $services = [];
    
    public function __set(string $name, object $service) {
        $this->services[$name] = $service;
    }
    
    public function __call(string $name, array $args) {
        if (isset($this->services[$name])) {
            return $this->services[$name]->handle(...$args);
        }
        throw new Exception("Service $name not registered");
    }
}

$facade = new ServiceFacade();
$facade->payment = new PaymentService();
$facade->notification = new NotificationService();
$facade->payment->charge(100); // 實際呼叫 PaymentService::charge
?>

3. 可呼叫策略物件

<?php
interface DiscountStrategy {
    public function __invoke(float $price): float;
}

class VipDiscount implements DiscountStrategy {
    public function __invoke(float $price): float { return $price * 0.8; }
}

class SeasonalDiscount implements DiscountStrategy {
    public function __invoke(float $price): float { return $price * 0.9; }
}

function applyDiscount(float $price, DiscountStrategy $strategy): float {
    return $strategy($price); // 直接呼叫物件
}

echo applyDiscount(100, new VipDiscount()); // 80
?>

4. 模板渲染物件

<?php
class View {
    private array $vars = [];
    public function __set(string $name, mixed $value) { $this->vars[$name] = $value; }
    public function __get(string $name) { return $this->vars[$name] ?? null; }
    public function __toString() { 
        extract($this->vars);
        ob_start();
        include $this->template;
        return ob_get_clean();
    }
}

$view = new View();
$view->title = '歡迎';
$view->user = ['name' => 'Alice'];
echo $view; // 自動渲染模板
?>

注意事項與效能

  • 魔術方法比直接存取慢(需經過方法解析),高頻路徑建議定義實際屬性/方法
  • __get/__set 遞迴呼叫會導致無限循環,內部存取私有陣列時用 $this->data[$name] 而非 $this->$name
  • __toString 必須回傳字串,拋出異常會導致 Fatal Error
  • __invoke 讓物件可當作 callable 傳遞,支援型別提示 callable|Closure|MyInvokable

PHP 8 新增:__serialize / __unserialize

<?php
class DataObject {
    private array $data;
    public function __serialize(): array { return $this->data; }
    public function __unserialize(array $data): void { $this->data = $data; }
}
// 取代舊有 __sleep/__wakeup,支援 typed properties、更安全
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved