PHP Statics

PHP’s use of static properties and methods not only require jedi-like mind tricks to grok, let’s face it: they’re just wrong. So, this post today is to help lay it out there. Let’s take this first example:

class MyClass {

    /* helper methods */
    public static function helper1() {
        echo "Calling My Helper 1\n";
    }

    public static function helper2() {
        echo "Calling My Helper 2\n";
    }

    /* instance methods */
    public function instanceMethod1() {
        echo "Calling Instance Method 1\n";
    }

    public function instanceMethod2() {
        echo "Calling Instance Method 2\n";
    }
}

echo "Instantiating MyClass\n";
$myInstance = new MyClass();
$myInstance->instanceMethod1();
$myInstance->instanceMethod2();
$myInstance->helper1();
$myInstance->helper2();

/*
Results in:
Instantiating MyClass
Calling Instance Method 1
Calling Instance Method 2
Calling My Helper 1
Calling My Helper 2
*/

Okay, so even though we declare methods ‘helper1′ and ‘helper2′ as static, we can call them on the instance. That’s fine — PHP will dynamically check if they are instance methods first, and then fall back to the static methods that are available. That’s actually handy, since the static methods won’t have a dependency on an instance, why shouldn’t we be able to use them both ways?

Now, let’s take this one:

class MyClass {

    /* helper methods */
    public static function helper1() {
        echo "Called My Helper 1\n";
    }

    public static function helper2() {
        echo "Calling My Helper 2\n";
        $this->instanceMethod2();
        // so, even though we called this static method on an instance,
        // it doesn't understand $this.
    }

    /* instance methods */
    public function instanceMethod1() {
        echo "Calling Instance Method 1\n";
        $this->helper1(); //this should be allowed
    }

    public function instanceMethod2() {
        echo "Called Instance Method 2\n";
    }
}

echo "Instantiating MyClass\n";
$myInstance = new MyClass();
$myInstance->instanceMethod1();
$myInstance->helper2();

/*
Results in:
Instantiating MyClass
Calling Instance Method 1
Called My Helper 1
Calling My Helper 2

Fatal error: Using $this when not in object context in ...
*/

This makes sense. even though we’re calling a static method on an instance, it prevents that method from having access to the instance ($this). This is preferred: we want our static methods to be useful in both a static and an instance context.

Now let’s take a look at something far less acceptable:

class MyClass {

    /* helper methods */
    public static function helper1() {
        echo "Called My Helper 1\n";
    }

    public static function helper2() {
        echo "Calling My Helper 2\n";
        self::instanceMethod2();

        // This is absolutely wrong.
        // You should not be able to call instance methods statically!!!!!!
    }

    /* instance methods */
    public function instanceMethod1() {
        echo "Calling Instance Method 1\n";
        self::helper1();
    }

    public function instanceMethod2() {
        echo "Called Instance Method 2\n";
    }
}

echo "Instantiating MyClass and calling methods statically on the instance\n";
$myInstance = new MyClass();
$myInstance::instanceMethod1();
$myInstance::helper2();

echo "Using MyClass statically\n";
MyClass::instanceMethod1();
MyClass::helper2();

/*
Results in:
Instantiating MyClass and calling methods statically on the instance
Calling Instance Method 1
Called My Helper 1
Calling My Helper 2
Called Instance Method 2

Using MyClass statically
Calling Instance Method 1
Called My Helper 1
Calling My Helper 2
Called Instance Method 2
*/

So, a couple things here…. As of PHP 5.3, we have been given the ability to store a class name in a variable, and then call methods off of it. Such as $class=”MyClass”; $class::myMethod(). This is cool, and actually quite handy. However, what I cannot fathom is why PHP gave us the ability to call instance methods statically off of an instance. It makes sense for methods declared statically to also advertise themselves as instance methods (as we covered previously), but for an instance method to advertise itself as a static one? That feels backwards. To that end, you’ll see that there are even cases where this will throw an (albeit meaningful) error:

class MyClass {

    protected $myVar = "You found me!";
    public function getMyVar() {
        echo "MyVar: " . $this->myVar . "\n";
    }
}

echo "Instantiating MyClass\n";
$instance = new MyClass();
$instance->getMyVar();

echo "Using MyClass instance, but calling method statically\n";
$instance = new MyClass();
$instance::getMyVar();

echo "Using MyClass";
MyClass::getMyVar();

/*
Results in:
Instantiating MyClass
MyVar: You found me!
Using MyClass instance, but calling method statically
Fatal error: Using $this when not in object context in ...
Using MyClass
Fatal error: Using $this when not in object context in ...
*/

As you can see, ‘getMyVar’ is not declared statically, but PHP, for whatever reason, has given us the option of calling it as such. This feels sloppy; it’s understood that static methods will NEVER have a dependency on an instance, but instance methods naturally tend to. As you can see by the error messages, this blatant mistake spews all over what used to be your nice, clean code; it throws a runtime error instead of a compile time error.

Inheritance. Now we get into an area where PHP has historically dropped the ball as far as statics go, but has begun to rectify with their new technique coined “Late Static Binding”.

class MyClass {

    protected static $myVar = "Base";

    public static function getMyVar() {
        echo "MyVar: " . self::$myVar . "\n";
    }
}

class MyClassChild extends MyClass {
    protected static $myVar = "Child";
}

echo "Using MyClass\n";
MyClass::getMyVar();
echo "Using MyClassChild\n";
MyClassChild::getMyVar();

/*
Results in:
Using MyClass
MyVar: Base
Using MyClassChild
MyVar: Base
*/

What we’re seeing here is that even though we’re calling ‘getMyVar’ on the child object, the parent’s use of ‘self’ will only look at the class on which it is defined. There must have been a public outcry or bloody coup since, as of PHP 5.3, the ‘static’ keyword was introduced to solve this problem. To quote php.net:

This feature was named “late static bindings” with an internal perspective in mind. “Late binding” comes from the fact that static:: will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It was also called a “static binding” as it can be used for (but is not limited to) static method calls.

If we replace ‘self::’ with ‘static::’, no other modifications need to be made to make it act as we’d expect it to.

Here’s another quirk — if you call an inherited static method, and that method requires knowing on what type of object it is being called (yes, I know that’s an anti-pattern, but hear me out), PHP’s get_class() will report it incorrectly. This goes back to the Late Static Binding, where get_class is a compile-time regurgitation of the state of your objects, and get_called_class is the runtime version:

class MyClass {
    public static $myVar = "You found me";
    public static function myFunction() {
        echo "You ran me\n";
    }
    public static function getClassInfo() {
        echo "Class Name: " . get_class() . "\n";
        echo "Called Class: " . get_called_class() . "\n";
    }
}

class MyClassChild extends MyClass {}

echo "Using MyClass\n";
echo MyClass::$myVar . "\n";
MyClass::myFunction();
MyClass::getClassInfo();

echo "Using MyClassChild\n";
echo MyClassChild::$myVar . "\n";
MyClassChild::myFunction();
MyClassChild::getClassInfo();

/*
Results in:
Using MyClass
You found me
You ran me
Class Name: MyClass
Called Class: MyClass

Using MyClassChild
You found me
You ran me
Class Name: MyClass
Called Class: MyClassChild
*/

This last example is a reiteration of a previous comment on “self::” vs. “static::”. I only bring this up because it recently burned me, and I am hoping to save someone else out there the pain and suffering I experienced in trying to hunt it down:

class MyClass {

    public static function step1() {
        echo "Base Step 1\n";
        self::step2();
    }

    public static function step2() {
        echo "Base Step 2\n";
        self::step3();
    }

    public static function step3() {
        echo "Base Step 3\n";
    }
}

class MyClassChild extends MyClass {

    public static function step2() {
        echo "Child Step 2\n";
        self::step3();
    }
}

echo "Using MyClass\n";
MyClass::step1();
echo "Using MyClassChild\n";
MyClassChild::step1();

/*
Results in:
Using MyClass
Base Step 1
Base Step 2
Base Step 3
Using MyClassChild
Base Step 1
Base Step 2
Base Step 3
*/

As you can see, by using “self::”, the parent step1() did not check to see if the called class (the child) had a more specific version of the step2() method, and it called its own instead. To fix that, we replace the “self::” with “static::” and it will work as we expect.</rant>

Leave a comment

0 Comments.

Leave a Reply


[ Ctrl + Enter ]