1

SO,

I have a problem with concept of PHP class method calling. Let we have a class that implementing simple sigma action, for example, a sum:

class Sigma
{
   protected $_fValue = null;

   public function __construct($fValue)
   {
      $this->_fValue = (double)$fValue;
   }

   public static function __callStatic($sName, $rgArgs)
   {
      if(count($rgArgs)!=count(array_filter($rgArgs, function($mArg)
      {
         return is_object($mArg) && $mArg instanceof self;
      })))
      {
         throw new Exception('Can not call method "'.$sName.'": invalid arguments');
      }
      if(!count($rgArgs))
      {
         return null;
      }
      $rResult = array_shift($rgArgs);
      foreach($rgArgs as $mArg)
      {
         $rResult = $rResult->$sName($mArg);
      }
      return $rResult;
   }

   public function getValue()
   {
      return $this->_fValue;
   }

   public function getSigma(self $rSigma)
   {
      return new self($this->getValue()+$rSigma->getValue());
   }

}

As you can see, we can use it's getSigma() method to return an instance of itself that represents simple sum of current object and input object:

$rFirst  = new Sigma(-4.5);
$rSecond = new Sigma(5);
$rThird  = $rFirst->getSigma($rSecond);
var_dump($rThird->getValue());//float(0.5) 

Well, everything is ok here. But as you can see, I've defined __callStatic() method in my class.

What I want to achieve - is that if I'll call my getSigma() method via static call, I would be able to pass any number of parameters since my __callStatic() method is expected to be triggered. But, unfortunately, this is not happening. Instead of this, PHP calls my getSigma() method directly as it was defined as static:

$rFirst  = new Sigma(-4.5);
$rSecond = new Sigma(5);
$rFourth = Sigma::getSigma($rFirst, $rSecond);//Fatal error: Using $this when not in object context in ...

And I understand why fatal error was triggered - PHP calls non-static method via :: operator - then, inside it , $this is used.

So to make long story short: is there a way to trigger __callStatic() method when non-static method is called as static? Or at least how to prevent that PHP 'bug-or-feature' with calling non-static methods via :: (and static methods via ->)?

All this I need to implement beautiful interface, when I can either use one method name for either object call or class call. And, since that is my goal, I can not make my non-static methods private or protected (i.e. they should be accessible for using outside)

Alma Do
  • 37,009
  • 9
  • 76
  • 105
  • Maybe reading the manual helps? *"The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope. "* That is directly from the page of __callStatic. So make it private, route it, add the other __call magic method and you can differ yourself. – hakre Sep 09 '13 at 08:14
  • @hakre actually, not. The question is about handling a case - when we have non-static method and calling it as a static (i.e. we have no instance, but somehow PHP calls that method - there's no context at all for doing that since there's no instance). Now I have an answer (and that includes explanation about how PHP stores methods in opcode - so I can understand why this 'bug-or-feature' is happening) – Alma Do Sep 09 '13 at 08:18
  • No, you used the `__callStatic` feature for a visible method. This does not work and you wrote you needed to google a lot, however actually this is written right on the page in the PHP manual. That's all I just commented and it still is the case. As the feature itself is - per the docs - the wrong tool, you can not expect it to work. This makes all additional discussion about some imaginated "context" somewhat moot as no such exists. – hakre Sep 09 '13 at 08:53
  • Regarding *"but somehow PHP calls that method"*: Yes, that is PHP 4 backwards compatibility. However if you have proper warning settings, you're notified about the problem in your code. Let me find a related question so that is better to understand. – hakre Sep 09 '13 at 08:56
  • But I'm not arguing with that. The question is not about this. It's about - how to _avoid_ this. I'm aware that it's happening and so I was seeking way to resolve this matter. Also for me was not (till this question - it's about _somehow_) clear - how PHP deals with magic method calling conditions, which are ambiguous: on one hand, there is a visible method, on the other hand - there's no corresponding static method. That's it. – Alma Do Sep 09 '13 at 08:56
  • Well, you can do some checks, however take care: http://3v4l.org/8WRQH – hakre Sep 09 '13 at 09:01
  • @hakre - yes, I did some checks, without success. As a result, asked on SO. Trick with `isset($this)` will not fit since I have to modify non-static method, unfortunately (and what if I have 100 such methods) – Alma Do Sep 09 '13 at 09:04
  • 100 such methods. Okay that really sounds like some design / code writing issue then. It's perhaps worth to break out of that model and consider thinking differently but I don't know your code. For historical reasons, just some reference: http://stackoverflow.com/a/18694918/367456 - the rest let's continue in PHP chat. – hakre Sep 09 '13 at 09:11

2 Answers2

1

Unfortunately not. As long as there is a method with the name you are trying to call, PHP will call it without considering type of call vs the type of method.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Yep, that's an answer I've got after having some time with googling and reading SO. A shame. Sometimes I hate OOP model in PHP – Alma Do Aug 08 '13 at 15:02
  • @AlmaDoMundo: What, you hate *only* the OO model? ;-) – Jon Aug 08 '13 at 15:03
0

Maybe it could solved by implementing __call. Like this:

public function __call($name, $args) {
    if (method_exists($this, "_$name") {
        call_user_func_array(array($this, "_$name"), $args); 
    } else {
    // no method found
    }
}

private function _getSigma($somearg) {
// ...
}

edit

So if you need class to extend or to implement something, try using somekind of wrapper, where you can mangle calls:

class SigmaWrapper {

    private static $_defaultClass = 'Sigma';
    private $_sigmaObject = null;

    public static function factory($sigmaObject) {
        // now would be good to implement some factory   
    }    

    private function setSigmaObject($obj) {
        $this->_sigmaObject = $arg;
    }

    public function __construct($arg) {
        if (is_object($arg)) {
          $this->setSigmaObject($arg);
        } else {
            $class = self::$_defaultClass;
            $this->_sigmaObject = new {$class}($fValue);
        }
    }

    public function __call($name, $args) {
        if (method_exists($this->_sigmaObject, "$name")) {
            // do whatever you need
            // call_user_func_array(array($this->_sigmaObject), "$name"), $args); 
        } else {
            // do whatever you need
            //call
        }
    }

    public function __callStatic($name, $args) {
        if (method_exists(get_class($this->_sigmaObject), "$name")) {
            // do whatever you need
            // call_user_func_array(array(get_class($this->_sigmaObject), "$name"), $args); 
        } else {
            // do whatever you need
            //call
        }
    }

    public function __get($name) {
        return $this->_sigmaObject->{$name};
    }

    public function __set($name, $value) {
        $this->_sigmaObject->{$name} = $value;
    }
}
kwarunek
  • 12,141
  • 4
  • 43
  • 48
  • No. That will not helps - as I need to have directly declared `getSigma()` method: my class is implementing some interface which contains declaration of `getSigma()` method – Alma Do Aug 08 '13 at 15:35
  • @Alma Do Mundo: Your class it *not* implementing that interface, otherwise you wouldn't expect the same method to be static and non-static. That would violate the interface. So consider where you make the difference, but you can't have non-difference and difference at once. Think about it a little. Changing the interface might help (e.g. make it a strategy?) – hakre Sep 09 '13 at 08:15
  • @hakre not sure what do you mean. [here](http://3v4l.org/pvFuU) is the sample. Yes, there _is_ an interface. I just wanted to extend my possibilities via described in question methods calling. Your argument is wrong here. I expect that static method and non-static are _not the same_ (the best will be a case when I can declare both methods - with the same name, but first as static and second as non-static. Since it's not possible in PHP, I've tried that way in question) – Alma Do Sep 09 '13 at 08:22
  • The interface has `public function getSigma($rSigma)` - this is a non-static method. You want this method as well static. This is violating the interface. Something is wrong: Either the interface or the class. – hakre Sep 09 '13 at 08:52
  • No, I want to provide another way to call it. (Remember - I'm treating static and non-static methods as a different entities and so, it's not a violation, it's extension - since you will be able to call non-static method as it was defined in interface) – Alma Do Sep 09 '13 at 09:14