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;
}