我似乎在 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
类,而不是简单地将其复制到A
和B
,就像我以前对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