1

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?

Community
  • 1
  • 1
Dmitry J
  • 867
  • 7
  • 20

0 Answers0