PHP 从 __getStatic() 返回对象实例


PHP return object instance from __getStatic()

PHP 有一个神奇的方法__getStatic(),允许重载静态方法调用。我有一个具有流畅接口的类,可以执行完整性检查。我这样称呼它:-

$check = new CheckSomeCondition();
$check->forActive()->sites(array(1,2,3))->check();

但是,我想这样称呼它:-

CheckSomeCondition::forActive()->sites(array(1,2,3))->check();

我认为在我的基类中使用这种神奇的方法可以让我做到这一点:-

public static function __callStatic($method, $args)
{
    $instance = new self();
    return call_user_func_array(array($instance, $method), $args);
}

但是new self()生成调用代码所在的类的实例,而不是__callStatic()存在的类,为什么会这样? 我该如何绕过它?

我也尝试过new static,这做了同样的事情。

我知道这肯定是可能的,因为 Laravel 的 QueryBuilder 有一个像 DB::table()->... 这样的接口,它使用方法链接,返回对象实例,而不是静态类。我看过 Laravel 代码,但我认为它们在应用程序中的其他地方创建实例,并且它们存储在准备返回的类成员中。

魔术方法__callStatic只为不存在的方法调用,所以在这种情况下它根本不会运行。

请考虑以下简化示例:

class Foo
{
    public function bar()
    {
        echo "Running instance method bar()";
    }
    public static function __callStatic($method, $args)
    {
        echo "__callStatic called for non-existent method $method";
    }
}
Foo::bar();

如果你运行这个(这是一个在线演示),你会看到它是调用的"真正的"bar()方法。

类上只能有一个名为 bar 的方法,所以 PHP 唯一的其他选择是抱怨bar()应该static - 它确实如此,但不是致命的。


您看到调用类的实例的原因不是$instance被实例化为错误的类,而是因为当非静态调用方法时,$this从封闭作用域"泄漏"。

在以下示例中,$this 最终成为 Bar 的实例:

class Foo
{
    public function doSomething()
    {
        echo get_class($this);
    }
}
class Bar
{
    public function doSomethingElse()
    {
        Foo::doSomething();
    }
}
$bar = new Bar();
$bar->doSomethingElse();

现场演示

正如@IMSoP所指出的,只有当没有调用名称的方法时,才会调用__getStatic() - 而不仅仅是在没有具有该名称的静态方法的情况下。

因此,允许调用(如 CheckClass::forActive->sites())的解决方法是为所有非静态方法名称提供一个前缀,例如"_",并有一个神奇的方法__call()该方法将添加前缀。

这意味着如果我确实CheckClass::forActive()该方法forActive()不存在,那么__getStatic()将被调用并将创建对象的实例并尝试调用所需的方法。但是该方法不存在,因为我们有前缀,所以 PHP 将调用 __call() magic 方法,该方法将添加前缀并调用前缀方法。

所以这两个函数是:-

public static function __callStatic($method, $args)
{
    $instance = new self;
    return call_user_func_array(array($instance, $method), $args);
}
public static function __call($method, $args)
{
    $method = 'prefix_' . $method;
    return call_user_func_array(array($instance, $method), $args);
}
// Then all our method names need to be prefixed, like so:-
public static function prefix_SomeMethod($method, $args)
{
    // Do something
    return $this;
}