问题
I seem to have trouble extending static classes in PHP.
PHP Code:
<?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";
}
}
?>
My calling code, and what I'd expect:
<?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'
?>
Actual complete output:
None (InstanceModule)
None (InstanceModule)
A (InstanceModule)
A (InstanceModule)
B (InstanceModule)
B (InstanceModule)
A (InstanceModule)
A (InstanceModule)
So what's going on here (from what it seems) is that as soon as I set self::$className
on either of the extending classes, it overrides the variable from the other class. I assume this is because I use static classes, and there can only be one InstanceModule
class instead of simply copying it to both A
and B
, as were my previous understanding of extends
. I've tried using the keyword static::$className
instead, but it seems to make no difference.
It'd be lovely if anyone could guide me in the right direction of what I'm doing wrong here, and what to do to fix this problem.
Edit: To clarify, this code does what I want, but is obviously a horrible workaround since it would ruin the whole idea of extending and reusing functions:
<?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";
}
}
?>
回答1:
Since $className is static and within the parent class, when you set className within A or B, it changes the variable within the parent, and the same is done when the variable is read. Unless you override className in your extended classes, you'll be storing and retrieving information from the same memory location, originally defined in InstanceModule.
If you redefine className in A/B, you can access className using parent:: or self:: from InstanceModule or A/B respectively. Depending on what you are trying to do, Abstract classes may also play a significant role.
See Static Keyword or Class Abstraction on the PHP5 Manual.
回答2:
I think the best answer to your situation is to use the get_called_class() function instead of your current $className
variable, which will return the late static binding class name instead of __CLASS__
or get_class()
which will only return the current class name.
If you changed your PrintClassName()
function to just output what get_called_class()
returned, your output would be the following. Now you'd just need to incorporate a default value, which of course will be shared across the classes, so you'd have to have that flag in both classes if you're going to continue using static methods.
A
B
A
B
A
B
A
B
回答3:
I would have the base class keep a repository of sub class instances, so that you can properly separate data that belongs to each class rather than pulling that data from a static base class variable.
You can use the __callStatic()
magic method on the base class to accomplish calling methods on non-existent sub classes, as demonstrated below. Unfortunately, the static repository variable needs to be declared public due to the visibility of that magic method.
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');
Output:
Creating instance of A
Base (A)
Creating instance of B
Base (B)
A: setting x := X
B: setting y := Y
回答4:
This appears to still be an issue in php 7.3 (7 years after the original question here was posted).
The answer using __callStatic
looks like it probably works, but it's pretty complicated for something that should be simple. And the other 2 answers don't seem to actually offer a working solution to the issue, so I'm including my own workaround as an answer here.
You can replace the static variable with a static array:
<?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";
}
}
?>
So the "static" value for each child class of InstanceModule
is stored under a key having the name of the class it originates from.
To me, it seems like a PHP bug that sibling classes would share static properties.... that should never happen, especially when the parent is abstract. This workaround isn't super pretty, but it's the only method I've gotten to work that isn't too complicated.
With this workaround, you get the desired results:
<?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'
?>
来源:https://stackoverflow.com/questions/11174496/extending-static-classes-in-php-avoiding-sharing-variables-for-multiple-classe