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.