ZetCode

PHP __invoke 方法

上次修改于 2025 年 5 月 28 日

在本文中,我们将探讨 PHP 的 __invoke 魔术方法,该方法允许将对象作为函数调用。当一个类实现了 __invoke 方法时,该类的实例就成为了可调用对象,可以像函数一样被调用。

__invoke 方法使对象能够在调用之间保持状态,同时提供类似函数的功能。 这一强大特性弥合了 PHP 中面向对象编程和函数式编程范式之间的差距。

PHP 的 __invoke 方法的优点是

通过使用 __invoke,开发人员可以创建更具表达性和灵活性的代码,结合对象和函数的优点。

__invoke 方法在类中定义,当一个对象像函数一样被调用时,该方法就会被调用。 它能接受任意数量的参数并返回任何值,就像一个普通函数一样。

简单的 Greeter 示例

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

greeter_invoke.php
<?php

declare(strict_types=1);

class Greeter {

    public function __invoke(string $name): string {
        return "Hello, $name!";
    }
}

$greet = new Greeter();
echo $greet('John') . "\n";

// Check if object is callable
var_dump(is_callable($greet));

// Using as a callback
$names = ['Alice', 'Bob', 'Charlie'];
$greetings = array_map($greet, $names);
print_r($greetings);

此示例演示了一个简单的 Greeter 类,其中实例可以像函数一样被调用以产生问候语。 它还展示了该对象作为回调函数与 array_map 一起使用。

λ php greeter_invoke.php
Hello, John!
bool(true)(
    [0] => Hello, Alice!
    [1] => Hello, Bob!
    [2] => Hello, Charlie!
)

带有多个参数的计算器

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

calculator_invoke.php
<?php

declare(strict_types=1);

class Calculator {
    
    public function __invoke(float $a, float $b, string $operation): float {
        return match($operation) {
            '+' => $a + $b,
            '-' => $a - $b,
            '*' => $a * $b,
            '/' => $a / $b,
            default => throw new InvalidArgumentException("Unknown operation")
        };
    }
}

$calc = new Calculator();
echo "5 + 3 = " . $calc(5, 3, '+') . "\n";
echo "5 * 3 = " . $calc(5, 3, '*') . "\n";

Calculator 类展示了 __invoke 如何处理多个参数以执行算术运算。

λ php calculator_invoke.php
5 + 3 = 8
5 * 3 = 15

与普通函数不同,具有 __invoke 的对象可以在调用之间保持内部状态。 这允许使用强大的模式,例如可配置函数或记住先前交互的类似函数对象。

带状态的计数器

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

counter_invoke.php
<?php

declare(strict_types=1);

class Counter {
    private int $count = 0;

    public function __invoke(): int {
        return ++$this->count;
    }

    public function reset(): void {
        $this->count = 0;
    }
}

$counter = new Counter();
echo $counter() . "\n";
echo $counter() . "\n";
echo $counter() . "\n";

$counter->reset();
echo $counter() . "\n";

Counter 类维护一个内部计数器,每次调用时都会递增,并通过一个方法允许重置。

λ php counter_invoke.php
1
2
3
1

可配置的记录器

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

logger_invoke.php
<?php

declare(strict_types=1);

class Logger {
    private string $prefix;

    public function __construct(string $prefix) {
        $this->prefix = $prefix;
    }

    public function __invoke(string $message): void {
        echo "[{$this->prefix}] " . date('Y-m-d H:i:s') . " - $message\n";
    }
}

$infoLogger = new Logger('INFO');
$infoLogger('Application started');

$errorLogger = new Logger('ERROR');
$errorLogger('Something went wrong!');

Logger 类可通过构造函数进行配置,允许不同的实例使用不同的前缀进行日志记录。

λ php logger_invoke.php
[INFO] 2025-05-28 00:00:00 - Application started
[ERROR] 2025-05-28 00:00:00 - Something went wrong!

缓存函数结果

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

cached_function_invoke.php
<?php

declare(strict_types=1);

class CachedFunction {
    private array $cache = [];
    private $function;

    public function __construct(callable $function) {
        $this->function = $function;
    }

    public function __invoke($arg) {
        if (!array_key_exists($arg, $this->cache)) {
            $this->cache[$arg] = ($this->function)($arg);
        }
        return $this->cache[$arg];
    }
}

$expensiveFunction = function($x) {
    sleep(1); // Simulate expensive computation
    return $x * 2;
};

$cached = new CachedFunction($expensiveFunction);

echo "First call (slow): ";
$start = microtime(true);
echo $cached(5) . " - ";
echo round(microtime(true) - $start, 3) . "s\n";

echo "Second call (fast): ";
$start = microtime(true);
echo $cached(5) . " - ";
echo round(microtime(true) - $start, 3) . "s\n";

CachedFunction 类对结果进行记忆,以优化昂贵计算的性能。

λ php cached_function_invoke.php
First call (slow): 10 - 1.003s
Second call (fast): 10 - 0.000s

实际用例

__invoke 方法在 PHP 开发中有很多实际应用。 以下是一些证明其特别有用的常见场景。

策略模式实现

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

discount_strategy.php
<?php

declare(strict_types=1);

interface DiscountStrategy {
    public function __invoke(float $amount): float;
}

class NoDiscount implements DiscountStrategy {
    public function __invoke(float $amount): float {
        return $amount;
    }
}

class PercentageDiscount implements DiscountStrategy {
    private float $percentage;

    public function __construct(float $percentage) {
        $this->percentage = $percentage;
    }

    public function __invoke(float $amount): float {
        return $amount * (1 - $this->percentage / 100);
    }
}

class FixedDiscount implements DiscountStrategy {
    private float $discount;

    public function __construct(float $discount) {
        $this->discount = $discount;
    }

    public function __invoke(float $amount): float {
        return max(0, $amount - $this->discount);
    }
}

function applyDiscount(float $amount, DiscountStrategy $discount): float {
    return $discount($amount);
}

$orderAmount = 100;
echo "No discount: " . applyDiscount($orderAmount, new NoDiscount()) . "\n";
echo "10% discount: " . applyDiscount($orderAmount, new PercentageDiscount(10)) . "\n";
echo "$20 discount: " . applyDiscount($orderAmount, new FixedDiscount(20)) . "\n";

此示例展示了使用 __invoke 的策略模式,以多态的方式应用不同的折扣策略。

λ php discount_strategy.php
No discount: 100
10% discount: 90
$20 discount: 80

中间件管道

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

middleware_invoke.php
<?php

declare(strict_types=1);

class Middleware {

    private $next;
    private $action;

    public function __construct(callable $action, ?callable $next = null) {
        $this->action = $action;
        $this->next = $next;
    }

    public function __invoke($request) {
        $result = ($this->action)($request);
        return $this->next ? ($this->next)($result) : $result;
    }
}

$middleware1 = new Middleware(
    fn($x) => $x * 2,
    new Middleware(
        fn($x) => $x + 5,
        new Middleware(
            fn($x) => $x / 3
        )
    )
);

echo "Middleware result: " . $middleware1(10) . "\n";

Middleware 类演示了一个简单的中间件管道,其中每个中间件都可以转换请求并将其传递给下一个中间件。 这种模式通常用于 Web 框架中,以模块化的方式处理请求和响应。

λ php middleware_invoke.php
Middleware result: 8.3333333333333

具有函数行为的值对象

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

money_invoke.php
<?php

declare(strict_types=1);

class Money {

    private float $amount;
    private string $currency;

    public function __construct(float $amount, string $currency) {
        $this->amount = $amount;
        $this->currency = $currency;
    }

    public function __invoke(float $rate): Money {
        return new Money($this->amount * $rate, $this->currency);
    }

    public function getAmount(): float {
        return $this->amount;
    }

    public function getCurrency(): string {
        return $this->currency;
    }
}

$usd = new Money(100, 'USD');
$converted = $usd(0.85); // Convert to EUR at 0.85 rate
echo "Converted amount: " . $converted->getAmount() . " " . $converted->getCurrency() . "\n";

Money 类使用 __invoke 根据给定的汇率转换金额,从而允许使用自然的类似函数语法。

λ php money_invoke.php
Converted amount: 85 USD

使用 __invoke 进行柯里化

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

__invoke 方法可用于实现柯里化,这是一种函数式编程技术,允许部分应用函数。 这意味着您可以通过固定现有函数的一些参数来创建新函数,从而实现更灵活和可重用的代码。 这对于从更通用的函数创建专用函数特别有用,从而实现更简洁和模块化的代码。

curry_invoke.php
<?php

declare(strict_types=1);

class Curry {

    private $function;
    private array $args;

    public function __construct(callable $function, array $args = []) {
        $this->function = $function;
        $this->args = $args;
    }

    public function __invoke(...$args) {
        $newArgs = array_merge($this->args, $args);
        $reflection = new ReflectionFunction($this->function);
        
        if (count($newArgs) >= $reflection->getNumberOfRequiredParameters()) {
            return $reflection->invokeArgs($newArgs);
        }
        
        return new self($this->function, $newArgs);
    }
}

$add = new Curry(function($a, $b, $c) {
    return $a + $b + $c;
});

$add5 = $add(5);
$add5And10 = $add5(10);
echo "Curried result: " . $add5And10(15) . "\n"; // 30

Curry 类允许部分应用函数。 它接受一个可调用对象并累积参数,直到提供了足够的参数来调用原始函数。 这使得可以从更通用的函数创建专用函数,从而实现更简洁和模块化的代码。

λ php curry_invoke.php
Curried result: 30

函数组合

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

__invoke 方法可用于将多个函数组合成一个可调用对象。 这允许链式操作,其中一个函数的输出成为下一个函数的输入。 这种技术对于创建数据转换或操作的管道非常有用。

compose_invoke.php
<?php

declare(strict_types=1);

class Compose {

    private array $functions;

    public function __construct(callable ...$functions) {
        $this->functions = $functions;
    }

    public function __invoke($value) {
        return array_reduce(
            array_reverse($this->functions),
            fn($carry, $fn) => $fn($carry),
            $value
        );
    }
}

$composed = new Compose(
    fn($x) => $x * 2,
    fn($x) => $x + 3,
    fn($x) => $x / 4
);

echo "Composed result: " . $composed(10) . "\n"; // ((10/4)+3)*2 = 11

Compose 类将多个函数链接在一起,按顺序应用它们。 每个函数的输出成为下一个函数的输入,从而以简洁易读的方式实现复杂的转换。

λ php compose_invoke.php
Composed result: 11

特定于领域的语言 (DSL)

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

__invoke 方法可用于创建流畅的接口或特定于领域的语言 (DSL),用于构建复杂的查询或配置。 这允许开发人员以更自然、更具可读性的方式表达操作。 例如,可以使用 __invoke 以流畅的风格构建 SQL 查询,从而实现查询构建器。

query_builder_invoke.php
<?php

declare(strict_types=1);

class QueryBuilder {
    private array $query = [];

    public function select(...$fields): self {
        $this->query['select'] = $fields;
        return $this;
    }

    public function where(string $field, string $operator, $value): self {
        $this->query['where'][] = [$field, $operator, $value];
        return $this;
    }

    public function __invoke(): string {
        $select = implode(', ', $this->query['select'] ?? ['*']);
        $where = '';
        
        if (!empty($this->query['where'])) {
            $conditions = array_map(
                fn($cond) => "{$cond[0]} {$cond[1]} '{$cond[2]}'",
                $this->query['where']
            );
            $where = ' WHERE ' . implode(' AND ', $conditions);
        }
        
        return "SELECT $select FROM table$where";
    }
}

$query = new QueryBuilder();
$sql = $query
    ->select('id', 'name', 'email')
    ->where('age', '>', 18)
    ->where('status', '=', 'active')
    (); // Invoke to get SQL

echo "Generated SQL: $sql\n";

QueryBuilder 创建一个流畅的 DSL,用于构建 SQL 查询。 它允许链接方法以指定字段和条件,并且 __invoke 方法在调用时生成最终的 SQL 字符串。

λ php query_builder_invoke.php
Generated SQL: SELECT id, name, email FROM table WHERE age > '18' AND status = 'active'

记忆化装饰器

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

__invoke 方法也可用于创建记忆化装饰器,用于缓存昂贵函数调用的结果。 这对于递归函数或计算密集型操作特别有用。 这种技术可以通过避免冗余计算来显着提高性能,尤其是在斐波那契数列或其他递归算法等情况下。

memoize_invoke.php
<?php

declare(strict_types=1);

class Memoize {
    private $function;
    private array $cache = [];

    public function __construct(callable $function) {
        $this->function = $function;
    }

    public function __invoke(...$args) {
        $key = serialize($args);
        
        if (!array_key_exists($key, $this->cache)) {
            $this->cache[$key] = ($this->function)(...$args);
        }
        
        return $this->cache[$key];
    }
}

$fibonacci = new Memoize(function($n) use (&$fibonacci) {
    return $n <= 1 ? $n : $fibonacci($n - 1) + $fibonacci($n - 2);
});

echo "Fibonacci(10): " . $fibonacci(10) . "\n";

Memoize 类通过缓存结果来优化斐波那契数列等递归函数。

λ php memoize_invoke.php
Fibonacci(10): 55

最佳实践

使用 PHP 的 __invoke 方法时,请遵循这些最佳实践,以确保代码的清洁、可维护和有效。

最佳实践示例

__invoke 方法通常用于创建行为类似于函数的对象。 当在需要函数的上下文中使用对象时,这允许使用更自然的语法,例如回调或高阶函数。

best_practices.php
<?php

declare(strict_types=1);

// 1. Use for single-purpose callable objects
class DiscountCalculator {
    public function __invoke(float $amount): float {
        return $amount * 0.9; // Example 10% discount
    }
}

// 2. Document behavior clearly
/**
 * Class that transforms strings according to configured rules
 * @method string __invoke(string $input) Transforms input string
 */
class StringTransformer {
    public function __invoke(string $input): string {
        return strtoupper($input);
    }
}

// 3. Consider implementing other interfaces too
interface LoggerInterface {
    public function log(string $message): void;
}

class Logger implements LoggerInterface {
    public function log(string $message): void {
        echo "LOG: $message\n";
    }
    public function __invoke(string $message): void {
        $this->log($message);
    }
}

// 4. Use type hints for parameters and return values
class Validator {
    public function __invoke(mixed $value): bool {
        return !empty($value);
    }
}

// 5. Keep __invoke focused
class OrderProcessor {
    public function __invoke(Order $order): void {
        $this->validate($order);
        $this->applyDiscounts($order);
        $this->save($order);
    }
    
    private function validate(Order $order): void { /* ... */ }
    private function applyDiscounts(Order $order): void { /* ... */ }
    private function save(Order $order): void { /* ... */ }
}

// 6. Consider alternatives for simple cases
$simpleGreet = fn($name) => "Hello, $name!";
echo $simpleGreet('Alice') . "\n";

// 7. Be consistent with invocation patterns
$logger = new Logger();
$logger('message'); // Preferred

// 8. Use for natural function-like behavior
$isPositive = new class {
    public function __invoke(float $n): bool {
        return $n > 0;
    }
};

var_dump($isPositive(5)); // true
var_dump($isPositive(-3)); // false

// 9. Use with PHP's functional programming features
$numbers = [1, -2, 3, -4, 5];
$positiveNumbers = array_filter($numbers, $isPositive);
print_r($positiveNumbers);

这些实践有助于确保您有效地使用 __invoke,同时避免常见的陷阱。 主要考虑因素包括单一职责、清晰的文档和适当的用例。

λ php best_practices.php
Hello, Alice!
LOG: message
bool(true)
bool(false)
Array
(
    [0] => 1
    [2] => 3
    [4] => 5
)

PHP 的 __invoke 魔术方法是一个强大的特性,它弥合了面向对象编程和函数式编程之间的差距。 需要记住的要点是

当您需要将状态与类似函数行为相结合的对象时,__invoke 方法特别有价值。 当适当地使用时,它使代码具有表现力,并且既强大又易于理解。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。 迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出 所有 PHP 教程