PHP methods that work in both instantiated and sta

2019-07-23 14:42发布

问题:

I'm trying to setup some PHP methods that are callable in instantiated and static contexts. What are some good ways to do this? For example I want to be able to do:

Foo::bar($item); 
foo($item)->bar();

I could setup two separate classes and have each function modify the thisArg and delegate to the other, but it seems like there's got to be a better way. The only way I could think of to do it with only one class would be something like this:

function foo($item = null) {
    return $item instanceof Foo ? $item : new Foo($item);
}

class Foo { 
    protected $wrapped;

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

    public function get() {
        return $this->wrapped;
    }

    public function bar($item = null) {
        isset($this) and $item = &$this->wrapped;
        // do stuff with $item
        return isset($this) ? $this : $item;
    }
}

If you look at the code of underscore.php they do something like that. I've read some related questions from a while back which point out that using isset($this) to determine the context can raise a warning, but it seems to work fine...any updated thoughts on that? Another possibility is could be to make two classes, one with all static version of the methods and then a second class that uses __call to delegate to the static method like:

class _Foo
{
        protected $wrapped;

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

        public function __call($method_name, $args) { 
            array_unshift($args, $this->wrapped);
            $this->wrapped = call_user_func_array('Foo::' . $method_name, $args);
            return $this;
        }


}

Ideas?

回答1:

http://www.php.net/manual/en/domdocument.loadxml.php does this, but when called from a static context, issues an E_STRICT.

Roughly, making a static and instance callable method will work for now, but that feature is likely to be removed. Perhaps there's another way to accomplish what you need?

edit: The functionality can be simulated with __call as you mention without throwing an E_STRICT, but you don't need two classes:

<? //PHP 5.4+
class mightBeInstance
{
    public function __call($name, $arguments)
    {
        if ($name === 'doSomething') {
            return static::doSomething($this);
        }
    }

    public static function doSomething(mightBeInstance $item = null)
    {
        if ($item === null) {
            $item = new static();
        }

        $item->didSomething = true; // Or whatnot

        return $item;
    }
}

var_dump(
    (new mightBeInstance)->doSomething(),
    mightBeInstance::doSomething()
);

?>


回答2:

This is the only reliable solution. It works with 5.3+ (except for that inline object instantiation at the bottom), but is somewhat unwieldy.

class foo {
    protected function __call($method, $args) {
        if ($method == 'bar') {
            return $this->bar($args[0]);
        }
    }
    protected function bar($baz) {
        return "object context: $baz\n";
    }

    public static function __callStatic($method, $args) {
        if ($method == 'bar') {
            return self::barStatic($args[0]);
        }
    }
    protected static function barStatic($baz) {
        return "static context: $baz\n";
    }
}

echo foo::bar('baz');
echo (new foo())->bar('baz');

NOT RECOMMENDED: The following works in PHP 5.6 but throws an E_DEPRECATED error with the message "Non-static method foo::bar() should not be called statically" when run in PHP 7.0. The problem isn't with the isset($this) as you say, but rather with having a single function do double duty: it's either static or it isn't. It is still supported in PHP 7.0, but you shouldn't rely on it.

class foo {
    public function bar($baz) {
        if (isset($this)) {
            return "object context: $baz\n";
        } else {
            return "static context: $baz\n";
        }
    }
}

echo foo::bar('baz');
echo (new foo())->bar('baz');

DOESN'T WORK: This throws a fatal error "Cannot redeclare foo::bar()" in both PHP 5.6 and PHP 7.0, but it would be ideal if you could do it this way instead.

class foo {
    public function bar($baz) {
        return "object context: $baz\n";
    }
    public static function bar($baz) {
        return "static context: $baz\n";
    }
}

echo foo::bar('baz');
echo (new foo())->bar('baz');

Perhaps in future versions once the deprecated usage has been removed we'll be able to do that.