I've seen topic about this, and I understand how it works.
But isn't it very confusing? Using self::someMethod()
we intend to stop polymorphic behavior, and we expect to not depend anymore from possible overrides in child classes. But this example (quite unnatural, but still) shows that this expectation can lead to unexpected bugs. Suppose, we have class hierarchy Shape->Rectangle->Square, and along with other thing have static methods for calculating areas:
abstract class Shape {
//"abstract" `area` function which should be overriden by children
public static function area(array $args) {
throw new Exception("Child must override it");
}
//We also provide checking for arguments number.
static function areaNumArgs(){
throw new Exception("Child must override it");
}
final static function checkArgsNumber (array $args) {
return count($args) == static::areaNumArgs();
//With 'static' we use late static binding
}
}
class Rectangle extends Shape {
static function areaNumArgs(){return 2;} //Arguments are lengths of sides
static function area(array $args) {
//Algorithm is stupid, but I want to illustrate result of 'self'
$n = self::areaNumArgs();
/*With 'self' we DON'T use polymorphism so child can
override areaNumArgs() and still use this method.
That's the point of 'self' instead of 'static', right?*/
$area = 1;
for($i = 0; $i<$n; $i++) $area *= $args[$i];
return $area;
}
//Let's wrap call to 'area' with checking for arguments number
static function areaWithArgsNumberCheck (array $args)
{
if(self::checkArgsNumber($args))
return self::area($args);
//We use 'self' everywhere, so again no problem with
//possible children overrides?
else
return false;
}
}
var_dump(Rectangle::areaWithArgsNumberCheck(array(3,2)));
//output is 6, everything OK.
//Now we have Square class.
class Square extends Rectangle {
static function areaNumArgs(){return 1;}
//For square we only need one side length
static function area(array $args) {
//We want to reuse Rectangle::area. As we used 'self::areaNumArgs',
//there is no problem that this method is overriden.
return parent::area(array($args[0], $args[0]));
}
static function areaAnotherVersion(array $args) {
//But what if we want to reuse Rectangle::areaWithArgsNumberCheck?
//After all, there we also used only 'self', right?
return parent::areaWithArgsNumberCheck(array($args[0], $args[0]));
}
}
var_dump(Square::area(array(3))); //Result is 9, again everything OK.
var_dump(Square::areaAnotherVersion(array(3))); //Oops, result is FALSE
This is because though Rectangle::areaWithArgsNumberCheck
used only 'self', it made call to Shape::checkArgsNumber
, which used static::areaNumArgs
. And this last call was resolved to Square
class, because self::
call forwarded static binding.
The problem is easily solved with using class name (Rectangle::
) instead of self::
So for me it looks like if there are some static
calls, then all calls in that class hierarchy should be made static
of use class names explicitly. You just never know where self::
call will be forwarded and what potentially overriden methods will be used. Am I right about this, or are there scenarios where forwarding static binding with 'self' may be useful and safe?