__callStatic没有被调用?

8

我知道以下内容可能会在其他地方引起问题,而且可能是不好的设计,但我仍然想知道为什么这样做失败了(为了自己的学习):

class Test {
    // singleton
    private function __construct(){}
    private static $i;
    public static function instance(){
        if(!self::$i){
            self::$i = new Test();
        }
        return self::$i;
    }
    // pass static requests to the instance
    public static function __callStatic($method, $parameters){
        return call_user_func_array(array(self::instance(), $method), $parameters);
    }
    private $someVar = 1;
    public function getSomeVar(){
        return $this->someVar;
    }
}

print Test::getSomeVar();

错误是在非对象上下文中使用$this 显然,$this在静态方法中无法使用,但静态方法通过call_user_func_array将其传递给实例方法调用,这应该使$this成为实例...
/编辑
我知道$this在静态上下文中不可用。 但是,这样可以工作: print call_user_func_array(array(Test::instance(),'getSomeVar'),array()); 这正是__callStatic重载中发生的事情,因此出了些问题...
/编辑2
范围处理方式确实很奇怪。 如果将单例实例移动到任何其他类,则它将按预期工作:
class Test {
    // singleton
    private function __construct(){}
    private static $i;
    public static function instance(){
        if(!self::$i){
            self::$i = new Blah();
        }
        return self::$i;
    }
    // pass static requests to the instance
    public static function __callStatic($method, $parameters){
        return call_user_func_array(array(static::instance(), $method), $parameters);
    }
}

class Blah {
    private $someVar = 1;
    public function getSomeVar(){
        return $this->someVar;
    }
}

print Test::getSomeVar();

引用 PHP 手册:__callStatic() 在静态上下文中调用不可访问方法时触发。 - 您的方法是公共的,将其更改为私有/受保护的,它将按您的期望工作 ;) 参考:https://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic - jave.web
(同时目标“静态”方法当然也应该被定义为“静态”) - jave.web
2个回答

11

在 PHP 中,你不能通过 __callStatic 调用使对象方法变为静态的。它只会在方法不存在(包括不可从调用上下文中访问)时才被调用。在你的情况中,Test::getSomeVar() 已经定义。

由于向后兼容性,PHP 不仅检查是否存在静态方法,而是检查是否普遍存在方法。

在你的情况下,你正在以静态方式调用非静态方法,因此 $this 未定义,因为尚未调用 __callStatic。如果你将警告和通知设置为最高级别(开发推荐),PHP 将会对此发出警告。

因此,正确的用法是:

echo Test::instance()->getSomeVar();

就像实现单例模式的任何其他方法一样。

所以你只是错误地使用了__callStatic,它仅适用于尚未定义的方法。选择另一种工具/设计来完成工作,比如你在Blah类中使用的聚合示例。

另外还要注意:

通常情况下,在 PHP 中应该防止使用任何静态上下文,特别是当你在这里利用大量的魔术功能时,这是另一种不好的做法。看起来你在代码完成之前就有一堆设计问题。这将增加遇到难以调试和维护的代码的可能性。但这只是为了让你不会说过一段时间你没有被警告。

如果你想了解更多信息,请参见谁需要单例?


@hakre 感谢您详细的回答 - 我会因为清晰度而接受。这不是我个人正在处理的事情,而是调查一些新的流行框架和微框架的结果。结果发现 Laravel,例如,这是进入 PHP 框架世界的一个备受推崇但相当新的参与者,使用了我最后编辑中描述的模式(他们称之为“负载”的 Blah 类)。您的帖子解释了为什么要分离静态实例。谢谢。 - momo
2
如果他们使用静态调用进行装饰,Laravel是一个创建全局变量的框架。通常情况下,PHP提供开箱即用的全局变量,这似乎是多余的。也许他们并不真正希望在代码的某个部分中使用全局变量,因此他们通过装饰添加静态调用,这是一种更常见的模式:http://en.wikipedia.org/wiki/Decorator_pattern - 然而,静态装饰器会破坏灵活性,这对于一个框架来说并不合适 - 另请参见http://i.imgur.com/RJEsz.png - hakre
1
外观模式最多只有一个方法。你的示例中提供的静态方法访问器减少了从对象中受益的灵活性,因此是单向的,我不会经常使用常用的模式名称,因为在基于类的编程和全局(静态)变量中,所有这些都朝着错误的方向发展。注意,在与其他开发人员讨论时,您可能会对这些术语感到困惑。 - hakre
@jave.web:是的,那是正确的,“_unaccessible_”是一个很好的描述,在某些情况下,例如private等,通常被称为“可见性”。然而,在我看来,“存在”和将某物定义为不存在并不本质上是错误的,特别是考虑到答案的背景,尽管正如您正确指出的那样,根据上下文和读者(包括所有身份、性别和性取向的潜水员和包容性)的不同,措辞可能存在不精确之处。 - hakre
@hakre 我很高兴我们意见一致 ;) - jave.web
显示剩余5条评论

1

它不是这样工作的。在 static 调用中,你没有实例、没有对象和没有 $this。只有静态方法才能使用静态调用,其中 $this 不可用。

文档说:

当在静态上下文中调用不可访问的方法时,将触发 __callStatic()。

我知道这不是你希望得到的答案。正如你预期的那样:在 PHP 中,你不需要重载,只需创建你的应用程序并将此事留出即可。


问题在于对“在静态上下文中”的理解有误。 “上下文”是指它被调用的“位置”。如果您从Widget对象内部调用Widget :: foo(),则正在对象上下文中调用它。 “::”并不意味着“静态调用”。 - Brad Kent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接