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?
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()
);
?>
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.