在 PHP 中扩展静态类 - 避免共享扩展类中多个类的变量


Extending static classes in PHP - Avoiding sharing variables for multiple classes in extended class

我似乎在 PHP 中扩展静态类时遇到了麻烦。

PHP代码:

<?php
    class InstanceModule {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
    }
    class A extends InstanceModule {
        public static function Construct() {
            self::$className = "A";
        }
    }
    class B extends InstanceModule {
        public static function Construct() {
            self::$className = "B";
        }
    }
?>

我的调用代码,以及我的期望:

<?php
    //PHP Version 5.3.14
    A::PrintClassName(); //Expected 'None' - actual result: 'None'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'
    A::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'None' - actual result: 'A'
    B::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'B'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'
    A::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'A'
?>

实际完整输出:

None (InstanceModule)
None (InstanceModule)
A (InstanceModule)
A (InstanceModule)
B (InstanceModule)
B (InstanceModule)
A (InstanceModule)
A (InstanceModule)

所以这里发生的事情(从表面上看)是,一旦我在任何一个扩展类上设置self::$className,它就会覆盖另一个类的变量。我认为这是因为我使用静态类,并且只能有一个InstanceModule类,而不是简单地将其复制到AB,就像我以前对extends的理解一样。我尝试使用关键字static::$className,但它似乎没有区别。

如果有人能指导我朝着正确的方向前进,我在这里做错了什么,以及如何解决这个问题,那就太好了。

编辑:澄清一下,这段代码做了我想要的,但显然是一个可怕的解决方法,因为它会破坏扩展和重用函数的整个想法:

<?php
    class A {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
        public static function Construct() {
            self::$className = "A";
        }
    }
    class B {
        public static $className = 'None';
        public static function PrintClassName() {
            echo self::$className . ' (' . __CLASS__ . ')<br />';
        }
        public static function Construct() {
            self::$className = "B";
        }
    }
?>

由于$className是静态的并且在父类中,因此当您在 A 或 B 中设置 className 时,它会更改父类中的变量,读取变量时也会这样做。除非重写扩展类中的 className,否则您将从最初在 InstanceModule 中定义的同一内存位置存储和检索信息。

如果在 A/B 中重新定义 className,则可以分别从 InstanceModule 或 A/B 中使用 parent:: 或 self:: 访问 className。根据您要执行的操作,抽象类也可能发挥重要作用。

请参阅 PHP5 手册中的静态关键字或类抽象。

这似乎仍然是 php 7.3 中的一个问题(在原始问题发布 7 年后)。

使用__callStatic的答案看起来可能有效,但对于应该简单的事情来说,它非常复杂。其他 2 个答案似乎实际上并没有为该问题提供可行的解决方案,因此我在此处包含我自己的解决方法作为答案。

您可以将静态变量替换为静态数组:

<?php
    class InstanceModule {
        public static $className = [];
        public static function PrintClassName() {
            $calledClass = get_called_class();
            if(empty(self::$className[$calledClass])){
              $thisClassName = "none";
            }else{
              $thisClassName = self::$className[$calledClass];
            }
            echo $thisClassName . ' (' . __CLASS__ . ')<br />';
        }
    }
    class A extends InstanceModule {
        public static function Construct() {
            $calledClass = get_called_class();
            self::$className[$calledClass] = "A";
        }
    }
    class B extends InstanceModule {
        public static function Construct() {
            $calledClass = get_called_class();
            self::$className[$calledClass] = "B";
        }
    }
?>

因此,InstanceModule的每个子类的"静态"值存储在具有其源自的类名称的键下。

对我来说,兄弟类共享静态属性似乎是一个 PHP 错误......这不应该发生,尤其是当父级是抽象的时。这种解决方法不是很漂亮,但它是我唯一不太复杂的方法。

使用此解决方法,您可以获得所需的结果:

<?php   
    A::PrintClassName(); //Expected 'None' - actual result: 'None'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'
    A::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'None' - actual result: 'None'
    B::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'
    A::Construct();
    A::PrintClassName(); //Expected 'A' - actual result: 'A'
    B::PrintClassName(); //Expected 'B' - actual result: 'B'
?>

我会让基类保留子类实例的存储库,以便您可以正确分离属于每个类的数据,而不是从静态基类变量中提取该数据。

可以在基类上使用 __callStatic() magic 方法,以完成对不存在的子类的调用方法,如下所示。不幸的是,由于该神奇方法的可见性,静态存储库变量需要声明为公共。

abstract class Base
{
    public static $repo = array();
    public static function __callStatic($name, $args)
    {
        $class = get_called_class();
        if (!isset(self::$repo[$class])) {
                echo "Creating instance of $class'n";
                self::$repo[$class] = new $class();
        }
        return call_user_func_array(array(self::$repo[$class], $name), $args);
    }
        protected function PrintClassName()
        {
                echo __CLASS__, " (", get_called_class(), ")'n";
        }
        protected abstract function Construct($a);
}
class A extends Base
{
        protected function Construct($a)
        {
                echo __CLASS__, ": setting x := $a'n";
        }
}
class B extends Base
{
        protected function Construct($a)
        {
                echo __CLASS__, ": setting y := $a'n";
        }
}
A::PrintClassName();
B::PrintClassName();
A::Construct('X');
B::Construct('Y');

输出:

Creating instance of A
Base (A)
Creating instance of B
Base (B)
A: setting x := X
B: setting y := Y

我认为对您的情况的最佳答案是使用 get_called_class() 函数而不是您当前的 $className 变量,这将返回后期静态绑定类名而不是 __CLASS__get_class() 只返回当前类名。

如果将PrintClassName()函数更改为仅输出get_called_class()返回的内容,则输出将如下所示。现在你只需要合并一个默认值,当然该值将在类之间共享,所以如果你要继续使用静态方法,你必须在两个类中都有这个标志。

A
B
A
B
A
B
A
B