Static properties and methods in PHP. Why do some PHP developers prefer static APIs? Php static methods

Late Static Binding (LSB) has been a hot topic of discussion for the last three years in PHP development circles (and we finally got it in PHP 5.3). But why is it needed? In this article, we will examine exactly how late static binding can greatly simplify your code.

At a PHP developer meeting held in Paris in November 2005, the topic of late static linking was formally discussed by the core development team. They agreed to implement it, along with many other topics that were on the agenda. Details were to be agreed upon through open discussions.

Since late static binding was announced as an upcoming feature, two years have passed. And finally LSB became available for use in PHP 5.3. But this event went unnoticed by developers using PHP, from the notes only a page in the manual.

In short, the new late static binding functionality allows objects to still inherit methods from parent classes, but in addition allows inherited methods to have access to static constants, methods and properties of the child class, not just the parent class. Let's look at an example:

Class Beer ( const NAME = "Beer!"; public function getName() ( return self::NAME; ) ) class Ale extends Beer ( const NAME = "Ale!"; ) $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n";

This code will produce the following result:

Beer is: Beer! Ale is: Beer!

Class Ale inherited method getName(), but at the same time self still points to the class in which it is used (in this case the class Beer). This remained in PHP 5.3, but the word was added static. Let's look at the example again:

Class Beer ( const NAME = "Beer!"; public function getName() ( return self::NAME; ) public function getStaticName() ( return static::NAME; ) ) class Ale extends Beer ( const NAME = "Ale!" ; ) $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n"; echo "Beer is actually: " . $beerDrink->getStaticName() ."\n"; echo "Ale is actually: " . $aleDrink->getStaticName() ."\n";

New keyword static indicates that it is necessary to use a constant of an inherited class, instead of a constant that was defined in the class where the method is declared getStaticName(). Word static was added to implement new functionality, and for backward compatibility self works the same as in previous versions of PHP.

Internally, the main difference (and, in fact, the reason why binding is called late) between these two access methods is that PHP will define the value for self::NAME during "compilation" (when PHP characters are converted into machine code that will be processed by the Zend engine), and for static::NAME the value will be determined at startup (at the moment when the machine code is executed in the Zend engine).

This is another tool for PHP developers. In the second part we will look at how it can be used for good.

Beginning with PHP 5.3.0, there was a feature called late static binding that can be used to obtain a reference to a callable class in the context of static inheritance.

More specifically, late static binding preserves the name of the class specified in the last "non-forwarded call". In the case of static calls, this is an explicitly specified class (usually to the left of the operator :: ); in the case of non-static calls, this is the class of the object. A "redirected call" is a static call starting with self::, parent::, static::, or, if we move up the class hierarchy, forward_static_call(). Function get_called_class() can be used to get a string with the name of the called class, and static:: represents its scope.

The name “late static binding” itself reflects the internal implementation of this feature. "Late binding" reflects the fact that calls through static:: will not be calculated relative to the class in which the called method is defined, but will be calculated based on information at runtime. This feature was also called "static binding" because it can be used (but does not have to) in static methods.

Restrictions self::

Example #1 Usage self::

class A (
echo __CLASS__ ;
}
public static function
test()(
self::who();
}
}

class B extends A (
public static function who() (
echo __CLASS__ ;
}
}

B::test();
?>

Using Late Static Binding

Later static binding attempts to overcome this limitation by providing a keyword that references a class called directly at runtime. Simply put, a keyword that will allow you to link to B from test() in the previous example. It was decided not to introduce a new keyword, but to use static, which is already reserved.

Example #2 Easy to use static::

class A (
public static function who() (
echo __CLASS__ ;
}
public static function
test()(
static::who(); // Late static binding applies here
}
}

class B extends A (
public static function who() (
echo __CLASS__ ;
}
}

B::test();
?>

Execution result this example:

Comment:

In a non-static context, the called class will be the one to which the object instance belongs. Because $this-> will try to call private methods from the same scope, usage static:: may give different results. Another difference is that static:: can only refer to static fields of a class.

Example #3 Usage static:: in a non-static context

class A (
private function foo() (
echo "success!\n" ;
}
public function test() (
$this -> foo();
static::foo();
}
}

class B extends A (
/* foo() will be copied to B, hence its scope is still A,
and the call will be successful*/
}

class C extends A (
private function foo() (
/* original method replaced; scope of new C method */
}
}

$b = new B();
$b -> test();
$c = new C();
$c -> test(); //not true
?>

The result of running this example:

success! success! success! Fatal error: Call to private method C::foo() from context "A" in /tmp/test.php on line 9

Comment:

The resolving region of late static binding will be fixed by the static call that computes it. On the other hand, static calls using directives such as parent:: or self:: redirect call information.

Example #4 Redirected and non-redirected calls

It's no secret that people like to ask tricky questions during interviews. Not always adequate, not always related to reality, but the fact remains a fact - they ask. Of course, the question is different, and sometimes a question that at first glance seems stupid to you is actually aimed at testing how well you know the language in which you are writing.

Let’s try to take apart one of these questions “piece by piece” - What does the word “static” mean in PHP and why is it used?

The static keyword has three different meanings in PHP. Let's look at them in chronological order, as they appeared in the language.

The first value is a static local variable

function foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 0 foo(); // 0

In PHP, variables are local. This means that a variable defined and given a value within a function (method) exists only during the execution of that function (method). When the method exits, the local variable is destroyed, and when it re-enters, it is created anew. In the code above, such a local variable is the $a variable - it exists only inside the foo() function and is created anew each time this function is called. Incrementing a variable in this code is meaningless, since on the very next line of code the function will finish its work and the value of the variable will be lost. No matter how many times we call the foo() function, it will always output 0...

However, everything changes if we put the static keyword before the assignment:

Function foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

The static keyword, written before assigning a value to a local variable, has the following effects:

  1. The assignment is performed only once, on the first call to the function
  2. The value of a variable marked in this way is saved after the function ends.
  3. On subsequent calls to the function, instead of assignment, the variable receives the previously stored value
This use of the word static is called static local variable.
Pitfalls of static variables
Of course, as always in PHP, there are some pitfalls.

The first stone is that only constants or constant expressions can be assigned to a static variable. Here's the code:
static $a = bar();
will inevitably lead to a parser error. Fortunately, starting with version 5.6, it has become possible to assign not only constants, but also constant expressions (for example, “1+2” or “”), that is, expressions that do not depend on other code and can be calculated at the compilation stage

The second stone is that methods exist in a single copy.
Here everything is a little more complicated. To understand the essence, here is the code:
class A ( public function foo() ( static $x = 0; echo ++$x; ) ) $a1 = new A; $a2 = new A; $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
Contrary to the intuitive expectation “different objects - different methods,” we clearly see in this example that dynamic methods in PHP “do not multiply.” Even if we have a hundred objects of this class, the method will exist in only one instance; it’s just that a different $this will be thrown into it with each call.

This behavior can be unexpected for a developer who is not prepared for it and can be a source of errors. It should be noted that class (and method) inheritance leads to the creation of a new method:

Class A ( public function foo() ( static $x = 0; echo ++$x; ) ) class B extends A ( ) $a1 = new A; $b1 = new B; $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

Conclusion: Dynamic methods in PHP exist in the context of classes, not objects. And only in runtime the substitution “$this = current_object” occurs

The second meaning is static properties and methods of classes

In the PHP object model, it is possible to set properties and methods not only for objects - instances of a class, but also for the class as a whole. The static keyword is also used for this:

Class A ( public static $x = "foo"; public static function test() ( return 42; ) ) echo A::$x; // "foo" echo A::test(); // 42
To access such properties and methods, double-colon (“Paamayim Nekudotayim”) constructs are used, such as CLASS_NAME::$Variablename and CLASS_NAME::Methodname().

It goes without saying that static properties and static methods have their own characteristics and pitfalls that you need to know.

The first feature is banal - there is no $this. Actually, this stems from the very definition of a static method - since it is associated with a class, not an object, the $this pseudo-variable, which points to the current object in dynamic methods, is not available. Which is completely logical.

However, you need to know that, unlike other languages, PHP does not detect the situation “$this is written in a static method” at the parsing or compilation stage. An error like this can only occur at runtime if you try to execute code with $this inside a static method.

Code like this:
class A ( public $id = 42; static public function foo() ( echo $this->id; ) )
will not cause any errors, as long as you do not try to use the foo() method inappropriately:
$a = new A; $a->foo(); (and immediately get “Fatal error: Using $this when not in object context”)

The second feature is that static is not an axiom!
class A ( static public function foo() ( echo 42; ) ) $a = new A; $a->foo();
That's it, yes. A static method, if it does not contain $this in the code, can be called in a dynamic context, like an object method. This is not a bug in PHP.

The reverse is not entirely true:
class A ( public function foo() ( echo 42; ) ) A::foo();
A dynamic method that does not use $this can be executed in a static context. However, you will receive a warning "Non-static method A::foo() should not be called statically" at level E_STRICT. It's up to you to decide whether to strictly follow code standards or suppress warnings. The first, of course, is preferable.

And by the way, everything written above applies only to methods. Using a static property via "->" is impossible and leads to a fatal error.

The third meaning, which seems to be the most difficult - late static binding

The developers of the PHP language did not stop at two values keyword“static” and in version 5.3 they added another “feature” of the language, which is implemented with the same word! It's called "late static binding" or LSB (Late Static Binding).

The easiest way to understand the essence of LSB is with simple examples:

Class Model ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) echo Model::getTable(); // "table"
The self keyword in PHP always means “the name of the class where this word is written.” In this case, self is replaced by the Model class, and self::$table is replaced by Model::$table.
This language feature is called "early static binding". Why early? Because the binding of self and a specific class name does not occur at runtime, but at earlier stages - parsing and compiling the code. Well, “static” - because we're talking about about static properties and methods.

Let's change our code a little:

Class Model ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) class User extends Model ( public static $table = "users"; ) echo User::getTable() ; // "table"

Now you understand why PHP behaves unintuitively in this situation. self was associated with the Model class when nothing was known about the User class, and therefore points to Model.

What should I do?

To solve this dilemma, a “late” binding mechanism was invented at the runtime stage. It works very simply - just write “static” instead of the word “self” and the connection will be established with the class that calls this code, and not with the one where it is written:
class Model ( public static $table = "table"; public static function getTable() ( return static::$table; ) ) class User extends Model ( public static $table = "users"; ) echo User::getTable() ; // "users"

This is the mysterious “late static binding”.

It should be noted that for greater convenience in PHP, in addition to the word “static” there is also special function get_called_class(), which will tell you which class is in context at the moment your code works.

Happy interviews!

I have long wanted to write on this topic. The first impetus was the article by Miško Hevery "Static Methods are Death to Testability". I wrote a response article, but never published it. But recently I saw something that can be called “Class-Oriented Programming”. This refreshed my interest in the topic and this is the result.

"Class-Oriented Programming" is when classes are used that consist only of static methods and properties, and an instance of the class is never created. In this article I will talk about:

  • it does not provide any advantages over procedural programming
  • don't give up objects
  • presence of static class members! = death to tests
Although this article is about PHP, the concepts apply to other languages ​​as well.

Dependencies

Typically, code depends on other code. For example:

$foo = substr($bar, 42);
This code depends on the $bar variable and the substr function. $bar is just a local variable defined a little higher up in the same file and in the same scope. substr is a PHP core function. Everything is simple here.

Now, this example:

Class BloomFilter ( ... public function __construct($m, $k) ( ... ) public static function getK($m, $n) ( return ceil(($m / $n) * log(2)); ) ... )
This little helper function simply provides a wrapper for a specific algorithm that helps calculate a good number for the $k argument used in the constructor. Because it must be called before the class instance is created, it must be static. This algorithm has no external dependencies and is unlikely to be replaced. It is used like this:

$m = 10000; $n = 2000; $b = new BloomFilter($m, BloomFilter::getK($m, $n));
This does not create any additional dependencies. The class depends on itself.

  • Alternative constructor. A good example is the DateTime class built into PHP. Its instance can be created by two in different ways:

    $date = new DateTime("2012-11-04"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    In both cases, the result will be a DateTime instance, and in both cases the code is bound to the DateTime class in one way or another. The DateTime::createFromFormat static method is an alternative object constructor that returns the same thing as new DateTime , but with additional functionality. Where you can write new Class , you can also write Class::method() . This does not create any new dependencies.

  • Other uses of static methods affect binding and may create implicit dependencies.

    A word about abstraction

    Why all this fuss with addictions? The ability to abstract! As your product grows, its complexity increases. And abstraction is the key to managing complexity.

    For example, you have an Application class that represents your application. It talks to the User class, which is a representation of the user. Which receives data from Database. The Database class needs a DatabaseDriver. DatabaseDriver needs connection parameters. And so on. If you just call Application::start() statically, which will call User::getData() statically, which will call the DB statically, and so on, and hope that each layer will sort out its dependencies, you can end up with a terrible mess if something goes wrong not like that. It's impossible to guess whether calling Application::start() will work because it's not at all obvious how the internal dependencies will behave. Even worse, the only way to influence the behavior of Application::start() is to change source code this class and the code of the classes that it calls and the code of the classes that call those classes... in the house that Jack built.

    The most effective approach when creating complex applications is to create separate parts that you can build on later. Parts that you can stop thinking about, that you can be confident in. For example, when calling a static Database::fetchAll(...) , there is no guarantee that a connection to the database has already been established or will be established.

    Function(Database $database) ( ... )
    If the code inside this function is executed, it means that the Database instance was successfully passed, which means that the Database object instance was successfully created. If the Database class is designed correctly, then you can be sure that the presence of an instance of this class means the ability to perform database queries. If there is no instance of the class, the function body will not be executed. This means that the function should not care about the state of the database; the Database class will do this itself. This approach allows you to forget about dependencies and concentrate on solving problems.

    Without the ability to not think about dependencies and the dependencies of those dependencies, it is almost impossible to write any complex application. Database can be a small wrapper class or a giant multi-layered monster with a bunch of dependencies, it can start out as a small wrapper and mutate into a giant monster over time, you can inherit the Database class and pass it to a descendant function, none of this matters to your function (Database $ database) , as long as the public interface of the Database does not change. If your classes are properly separated from the rest of the application using dependency injection, you can test each of them using stubs instead of their dependencies. Once you've tested the class enough to make sure it works as expected, you can take the guesswork out of it simply by knowing that you need to use a Database instance to work with the database.

    Class-oriented programming is stupid. Learn to use OOP.

    Share