1

I am trying to make it so that I can call a non-static method statically using __callStatic but I keep getting this error. I know it is possible because some frameworks (like Laravel) does this but I can't seem to be able to do it myself.

Fatal error: Uncaught Error: Using $this when not in object context in Output.php:18 Stack trace: index.php(4): Output::prepare() 1 {main} thrown in Output.php on line 14

index.php

$output = Output::prepare();

Output.php

class Output
{
    public static function __callStatic($method, $args)
    {
        if ($method == 'prepare') {
            $obj = new static();
            return $obj->$method(...$args);
        }
    }

    public function prepare()
    {
        // (!) Uncaught Error: Using $this when not in object context
        if ($this->hasItems()) {
            return print_r($_POST, true);
        }
    }

    public function hasItems()
    {
        return true;
    }
}
Liga
  • 3,291
  • 5
  • 34
  • 59

4 Answers4

2

"__callStatic() is triggered when invoking inaccessible methods in a static context."

Your method prepare is accessible by its visibility. You would need to adjust the visibility of the prepare method if you want __callStatic to be used:

protected function prepare()

Your error sounds more like you declared prepare to be a static function:

public static function prepare()

Instead of Non-static method like:

public function prepare()

If your method was Non-Static you would be receiving something like this instead:

Non-static method Output::prepare() should not be called statically ...

PHP Manual - OOP - Magic Methods - __callStatic

lagbox
  • 48,571
  • 8
  • 72
  • 83
  • nice correct answer, but i guess it can be a bit confusing for new devs. – N69S Nov 18 '19 at 11:04
  • You say that because it should trigger a fatal error on the call instead of the error on `$this`. That interaction existed on old php version and i think you can still make it happen via `php.ini` configuration. – N69S Nov 18 '19 at 11:09
  • That's what i'm saying, it was the default interaction before in php <5.3, and is still possible via modifying configuration to trigger only a warning and not a fatal error. – N69S Nov 18 '19 at 11:26
  • The default interaction was if a public function exists and is called statically, it would proceed with the call of the method and throw a warning for calling it statically. – N69S Nov 18 '19 at 11:32
2

You can't use that trick on the same class unless you remove visibility to your methods as @lagbox says. Otherwise, PHP will call directly the prepare() method without going through __callStatic().(in your case it leads to $this error with a warning for using a public method statically)

class Output
{
    public static function __callStatic($method, $args)
    {
        if ($method == 'prepare') {
            $obj = new static();
            return $obj->$method(...$args);
        }
    }

    private function prepare()//or protected to remove visibility and force the call for __callStatic()
    {
        if ($this->hasItems()) {
            return print_r($_POST, true);
        }
    }

    public function hasItems()
    {
        return true;
    }
}

If you want to use the Facade trick, you need to forward the static call to another class (you can send some context to this second class to make it unique to Output class)

class Output
{
    public static function __callStatic($method, $args)
    {
        if (!method_exists(self::class,$method)) {
            return (new Forward('output'))->$method(...$args);
        }
    }
}

class Forward
{
    private $context;

    public function __construct($context = null)
    {
        $this->context = $context;
    }

    public function prepare()
    {
        if ($this->hasItems()) {
            return print_r($_POST, true);
        }
    }

    public function hasItems()
    {
        return true;
    }
}

PS: laravel uses this trait to get this done on modelsIlluminate\Support\Traits\ForwardsCalls.

protected function forwardCallTo($object, $method, $parameters)
{
    try {
        return $object->{$method}(...$parameters);
    } catch (Error | BadMethodCallException $e) {
        $pattern = '~^Call to undefined method (?P<class>[^:]+)::(?P<method>[^\(]+)\(\)$~';

        if (! preg_match($pattern, $e->getMessage(), $matches)) {
            throw $e;
        }

        if ($matches['class'] != get_class($object) ||
            $matches['method'] != $method) {
            throw $e;
        }

        static::throwBadMethodCallException($method);
    }
}
N69S
  • 16,110
  • 3
  • 22
  • 36
1

__callStatic() is triggered when invoking inaccessible methods in a static context.

You try use Output::prepare(). Magic Methods __callStatic not run. Because, you have been method prepare and it run method prepare instead of __callStatic. When throw a error:

Fatal error: Uncaught Error: Using $this when not in object context in Output.php

If you using it that similar in Laravel. Example for reference:


<?php

class Output
{
    public static function __callStatic($method, $args)
    {
        if ($method == 'prepare') {
            $obj = new Fix;
            return $obj->$method(...$args);
        }
    }
}

class Fix 
{

    public function prepare()
    {
        if ($this->hasItems()) {
            echo "Prepare";
        }
    }

    public function hasItems()
    {
        return true;
    }
}

$output = Output::prepare();

Or specific:

<?php
class OutputFacade extends Facade{

    protected static function getClass()
    {
        return new Output();   
    }
}
abstract class Facade
{
    public static function __callStatic($method, $args)
    {
        $instance = static::getClass();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
}

class Output 
{

    public function prepare()
    {
        if ($this->hasItems()) {
            echo "Prepare";
        }
    }

    public function hasItems()
    {
        return true;
    }
}

$output = OutputFacade::prepare();

Note: My english is not good, hope you understand

MinIsNghia
  • 64
  • 3
0

The method prepare inside your Output class is not static. It's only non static method you can call using $this-> in your case to call prepare you should make use of your Output class Use Output, then you can say $output = new Output and then $output->prepare(). If you want to use it as Output::prepare() you have to change the method inside your Output class to public static function prepare().

Hope that helps

Michel
  • 1,065
  • 1
  • 10
  • 25