3

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

4 Answers 4

4

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Class Abstraction doesn't seem to make a difference for me (although, I might be doing it wrong). As mentioned in the OP, I already have tried to use the static keyword, and lastly, redefining className in both extending classes (public static $className = 'None') actually results in PrintClassName()` to output None all the time, even though self::$className is still being defined.
This really helped me understand public static's using inheritance.
3

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'
?>

Comments

2

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

Comments

1

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

4 Comments

Of course the A and B is just a form of showing what was going on. In my actual scenario I deal with different extends InstanceModule classes, but they have different functions (and their common functions are in InstanceModule). This means that when I construct class B, and try to access A::FunctionInAClass I get an error, seeing as this function doesn't exist (as it really only has A::FunctionInBClass at this point, even though that function was declared in B and not A (but A was overwritten by B just like in my OP example).
It really looks like you'd be better off changing these classes from being static so you can instantiate objects on them, which would then yield the inheritance you're looking for.
The fact is that it's actually my try of making a more global way of calling classes (and instances). Sure I could call $db = DB::GetInstance(); $db->Connect();, but what I've tried to do in my code is have a wrapper that simply uses the current instance, and when you then run DB::Connect, it does this stuff in the background. Hence this problem comes. I guess I have to resolve to either using $GLOBALS (yuck) or having to get the instance whenever I need to call the class.
You should look into either the dependency injection or factory design patterns. Either or would be able to help you with the latter (getting instances when you need to call the class).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.