static in PHP

static keyword is used to represent class members, i.e. members which are shared among the class, as opposed of instance members which is unique to an object instance, in many programming languages, including PHP. However, the static functionality in PHP is much more powerful than in some other traditional programming languages, for example, C++ and Java, because in PHP polymorphism can also be achieved using static members in addition to instance members.

In C++ and Java, a static member is always resolved at compile time using the declared type. Using the following code as an example:

C++ code:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Base {
public:
    static string hello() {
        return message;
    }
protected:
    static string message;
};

class Intermediate : public Base {
protected:
    static string message;
};

class Derived : public Intermediate {
public:
    static string hello() {
        return "Derived: " + message;
    }
protected:
    static string message;
};

string Base::message = "Hello!";
string Intermediate::message = "Howdy!";
string Derived::message = "Hi!";

void test(Base *object) {
    cout << Intermediate::hello() << '\n';
    cout << object->hello() << '\n';
}

int main() {
    test(new Derived());
    return 0;
}

Java code:

class Base {
    protected static String message = "Hello!";
    public static String hello() {
        return message;
    }
}

class Intermediate extends Base {
    protected static String message = "Howdy!";
}

class Derived extends Intermediate {
    protected static String message = "Hi!";
    public static String hello() {
        return "Derived: " + message;
    }
}

public class Main {
    private static void test(Base object) {
        System.out.println(Intermediate.hello());
        System.out.println(object.hello());
    }

    public static void main(String[] args) {
        test(new Derived());
    }
}

Both programs outputs

Hello!
Hello!

when run, indicating Base::hello() is called with message resolved to Base::message inside Base::hello(). It is because, in both C++ and Java, all static members are resolved using the static (i.e. declared) type of the expression, which means object->hello() is the same as Base::hello() because object is declared to be of type Base *. Moreover, inside Base::hello(), message refers to Base::message because the scope is in class Base. The fact that hello() is called using Intermediate::hello() does not matter.

Now consider the following piece of similar PHP code:

<?php
class Base {
    protected static $message = "Hello!";
    public static function hello() {
        return self::$message;
    }
}

class Intermediate extends Base {
    protected static $message = "Howdy!";
}

class Derived extends Intermediate {
    protected static $message = "Hi!";
    public static function hello() {
        return "Derived: " . self::$message;
    }
}

function test(Base $object) {
    echo Intermediate::hello() . "\n";
    echo $object->hello() . "\n";
}

test(new Derived());

Running the PHP code gets:

Hello!
Derived: Hi!

which indicates that Derived::hello() get called in $object->hello() despite $object being declared as a Base in the function header. It is because, despite the type declaration in the function header, PHP is a dynamically-typed language where types are only associated with value but not the expression itself, which means the expression $object is not associated with a type without regarding to its value. The type declaration is only checked when an argument is passed into it at the point of calling and unused afterwards. It is not an error to reassign $object to something else inside the function. Therefore, when calling $object->hello(), the type of $object is retrieved from the $object at runtime, therefore, Derived::hello() is called because $object is a Derived.

For the call to Intermediate::hello(), the effect is the same as in C++ and Java, where the keyword self resolves to the class where the function is in. However, if we replace the keyword self by static, magic happens, which works for PHP version at least 5.3:

<?php
class Base {
    protected static $message = "Hello!";
    public static function hello() {
        return static::$message;
    }
}

class Intermediate extends Base {
    protected static $message = "Howdy!";
}

class Derived extends Intermediate {
    protected static $message = "Hi!";
    public static function hello() {
        return "Derived: " . static::$message;
    }
}

function test(Base $object) {
    echo Intermediate::hello() . "\n";
    echo $object->hello() . "\n";
}

test(new Derived());

Running the above code shows:

Howdy!
Derived: Hi!

Now, in the call of Intermediate::hello(), Intermediate::$message is returned from the function! The $message member in Intermediate has overridden the Base member and polymorphism is exhibited as the function in Base got the value of Intermediate::$message.

The magic is that, starting from PHP 5.3, static function calls have an implicit static scope, just like non-static function calls having an implicit $this variable. The static scope is set by the language to be the class where the function is called, i.e., in the call of Intermediate::hello() the static scope is Intermediate, and in the call of $object->hello() the static scope is Derived because $object is of type Derived (compared to the fact that, if hello() were non-static, $this would be set to $object). Furthermore, calling methods using self::, static:: or parent:: also forwards the static scope to the called method.

Using this feature, it is possible to do class-based polymorphism in PHP using static methods, properties and class constants, which serves as a complement to normal polymorphism using objects. In modern PHP version static methods are subject to compatibility rules regarding function signature if overriden, can be declared abstract, and can be placed inside interfaces, just like non-static functions. You can even use new static in factory methods to create objects of child classes without any knowledge of them as long as the child constructor is compatible with the parent constructor, which is impossible in Java without using reflection.