Menu
CoddyTech

__call 和 __callStatic

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 47 课,共 91 课。

就像 __get() 和 __set() 会拦截属性访问一样,__call() 和 __callStatic() 方法会拦截方法调用。当你调用对象或类中不存在或无法访问的方法时,就会触发它们。

__call() 方法处理实例方法调用。它会接收方法名和参数数组:

<?php
class Messenger {
    public function __call(string $name, array $arguments): string {
        return "Called '$name' with: " . implode(", ", $arguments);
    }
}

$msg = new Messenger();
echo $msg->sendEmail("Hello", "World");

输出:

Called 'sendEmail' with: Hello, World

对于静态方法调用,请使用 __callStatic()。注意,它必须声明为 static:

<?php
class QueryBuilder {
    public static function __callStatic(string $name, array $arguments): string {
        if (str_starts_with($name, "findBy")) {
            $field = substr($name, 6);
            return "Finding by $field: " . $arguments[0];
        }
        return "Unknown method: $name";
    }
}

echo QueryBuilder::findByEmail("test@example.com");

输出:

Finding by Email: test@example.com

这些魔术方法功能强大,可用于创建流畅的 API、实现方法转发,或构建动态查询生成器,将方法名称转换为数据库操作。它们让对象能够智能地响应任意方法调用,即使这些方法并未明确定义。

challenge icon

挑战

简单

让我们构建一个动态 API 客户端,使用魔术方法灵活地处理方法调用。你将创建一个类,用于拦截实例方法调用和静态方法调用,并将它们转换为有意义的、类似 API 的响应,而无需显式定义每个方法。

你需要将代码分布在两个文件中:

  • ApiClient.php:创建一个 ApiClient 类,使用魔术方法模拟 API 交互。这个类应有一个私有 $baseUrl 属性,通过构造函数设置。实现两个魔术方法:
    • __call():处理实例方法调用。当调用以 get 开头的方法(例如 getUsers 或 getPosts)时,提取资源名称(即 "get" 后面的部分),并返回 "GET [baseUrl]/[resource]: [arguments joined by comma]"。资源名称应转换为小写。如果方法不以 "get" 开头,则返回 "Unknown action: [methodName]"。
    • __callStatic():处理静态方法调用。当调用以 create 开头的方法(例如 createUser)时,提取资源名称并返回 "POST /[resource]: [arguments joined by comma]"。资源名称应为小写。对于以 delete 开头的方法,返回 "DELETE /[resource]: [first argument]"。对于其他任何方法,返回 "Static action not supported: [methodName]"。
  • main.php:引入 ApiClient 文件。你将收到 3 个输入:基本 URL、资源标识符和值。使用基本 URL 创建一个 ApiClient 实例。然后打印 4 行:
    • 调用一个动态 get 方法,将资源标识符用作方法名称的后缀(例如,如果资源是 "Users",就调用 getUsers),并将该值作为参数
    • 调用 getProducts,并传入两个参数:"electronics" 和 "active"
    • 调用静态方法 createOrder,并将该值作为参数
    • 调用静态方法 deleteItem,并将资源标识符作为参数

魔术方法让你的 API 客户端能够动态响应任何方法调用。你不必分别定义 getUsers()、getPosts()、getProducts() 等几十个方法,只需使用一个 __call() 方法检查方法名称并提取相关信息,就能处理所有这些调用。

亲手试试

<?php

require_once 'ApiClient.php';

// 读取输入
$baseUrl = trim(fgets(STDIN));
$resource = trim(fgets(STDIN));
$value = trim(fgets(STDIN));

// TODO: 使用基础 URL 创建一个 ApiClient 实例

// TODO: 以 resource 作为方法名后缀,调用动态 get 方法
// 提示:使用变量方法调用,例如 $api->{"get" . $resource}($value)

// TODO: 调用 getProducts,传入 "electronics" 和 "active" 两个参数

// TODO: 调用静态方法 createOrder,将 value 作为参数传入
// 提示:使用 ApiClient::createOrder($value)

// TODO: 调用静态方法 deleteItem,将 resource 作为参数传入

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器