Bug #81436 [Com]: Unexpected behavior when utilizing __call and __callStatic from different scope
| From: | dev98 at outlook dot de | Date: | Thu, 16 Sep 2021 07:22:50 +0000 |
| Subject: | Bug #81436 [Com]: Unexpected behavior when utilizing __call and __callStatic from different scope | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-236637@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=81436&edit=1
ID: 81436
Comment by: dev98 at outlook dot de
Reported by: dev98 at outlook dot de
Summary: Unexpected behavior when utilizing __call and
__callStatic from different scope
Status: Feedback
Type: Bug
Package: Scripting Engine problem
Operating System: Windows x64, Manjaro Linux x64
PHP Version: Irrelevant
Block user comment: N
Private report: N
New Comment:
Requinix thanks for the answers. I really appreciate your effort and time put into it. I fully
understand your points.
Maybe the php.net documentation just needs a few additional notes regarding the forward_static_call
and __callStatic topic, as you stated that there have been a few reports about that in the past.
Previous Comments:
------------------------------------------------------------------------
[2021-09-14 14:47:28] requinix@php.net
> If you replace the A::BLA code with:
> forward_static_call(array("A", "BLA"))
> I feel it should definitely call the static method BLA on A and not anything else.
Consider that "static" methods are not so much "this method only exists
statically" but rather "this method can be called statically". Static-ness is more
like an invocation capability than it is a requirement; you can call static methods with
"$this->" just fine (though it's poor practice to do so).
https://3v4l.org/uO01h
So it's not quite correct to say "the static method BLA".
If you follow that train of thought further, a forward_static_call()ed A::BLA is very much like a
regular A::BLA call. The problem that forward_static_call() solves is one that stems from the fact
that PHP allows static things to be inherited down a class hierarchy [1], and that otherwise there
is no way for a child class to call a parent class's static method while retaining the
child's "static" information.
If that's confusing, think about what happens with object instances. $this always refers to the
child-est members in the inheritance hierarchy <https://3v4l.org/H1T04>. Static members need something similar.
Before PHP 5.3 and late static bindings [2], there was no similar mechanism for static members.
"self" always indicated the class where it was used. LSB changed how static members were
handled internally and introduced/borrowed "static" to be like the static version of
$this, ie. to refer to the child-est members.
...except there's a slight problem here because PHP needs some way to know who that so-called
child is. It was already very capable of handling that with instance members because there's,
you know, the instance. But there is no instance in memory when dealing with static method calls.
Thus LSB's "runtime information" [3] to fill in the gap. Then forward_static_call()
was created to provide users with a way of saying that a static method somewhere should be invoked,
except rather than *reset* the "runtime information" like would normally happen with a
static call [4], PHP should *retain and reuse* it.
I think I may have digressed a bit...
> Or asked differently: How would one call A::BLA from an object context? Is this impossible?
You mean is it possible here to trigger A's __callStatic() with $name="BLA"?
As far as I know: no, it is not currently possible to convince PHP to execute in that way.
I see bug #65466 but I feel like I've seen one or two bug reports/feature requests about the
same phenomenon more recently than that. Also, a concrete use case would be helpful - especially one
that can't be resolved in a more logical way than needing __callStatic.
On that note, keep in mind that __callStatic does not mean "you attempted to call a static
method which did not exist". It means "you attempted to call a method which did not exist
and at a time when you did not have an instance of the class on hand". Remember what I said at
the beginning of this wall of text: static does not mean it's *only* static but that it *can
be* invoked statically.
Personally, I don't use __callStatic at all. Too many nuances about how and when static things
work.
[1] Don't care for it myself, but whether this makes sense or not is beside the point.
[2] https://www.php.net/manual/en/language.oop5.late-static-bindings.php
[3] In the LSB docs,
> This feature was named "late static bindings" with an internal perspective in
> mind. "Late binding" comes from the fact that static:: will not be resolved
> using the class where the method is defined but it will rather be computed
> using runtime information.
[4] IIRC there used to be ways to trick PHP into leaking $this or a static method's scope into
unrelated code, but I believe those have all been patched/fixed/removed.
------------------------------------------------------------------------
[2021-09-14 13:31:27] nerdbeere2k at gmail dot com
I fail to understand why this works the way it does as well.
If you replace the A::BLA code with:
forward_static_call(array("A", "BLA"))
I feel it should definitely call the static method BLA on A and not anything else.
Or asked differently: How would one call A::BLA from an object context? Is this impossible?
------------------------------------------------------------------------
[2021-09-14 13:16:43] requinix@php.net
Of course it's only once I hit the Submit button do I see an edit to make:
> the double colon is to resolve how a method should be called
Makes more sense to say it resolves *where* a method should be called.
------------------------------------------------------------------------
[2021-09-14 13:13:20] requinix@php.net
> and you might argue that "that's not a static call operator, its a scope resolution
> operator."
But that's exactly the answer: the double colon is to resolve how a method should be called,
not to do a static call. "A::BLA" will resolve to an instance call if you're in the
scope of an instance of A or a static call if not.
A::doMagicCall_from_staticContext:
- has no instance of A so A::BLA() is a static method call
- has no instance of B so B::BLA() is a static method call
A->doMagicCall_from_objectContext:
- inside an instance of A so A::BLA() is an instanced method call
- has no instance of B so B::BLA() is a static method call
Look at the typical child constructor pattern:
public function __construct() {
parent::__construct();
$this->foo = "bar";
}
parent::__construct is not called statically, and neither "parent" nor
"__construct" have any kind of special meaning that would secretly change ::'s scope
resolution process to work differently than it normally would.
__call and __callStatic only enter the picture at the very last minute when PHP is actually trying
to call the method and when it finds out that "BLA" does not exist. By that point it has
already decided whether "BLA" should be an instanced call or not, and it will select
__call or __callStatic appropriately.
------------------------------------------------------------------------
[2021-09-14 11:20:43] dev98 at outlook dot de
Description:
------------
When we utilize the __callStatic function with an appropriate static call on the same class the
magic function is defined while being in an object scope, for some reason, the __call method will
get invoked. This seems very unintuitive as the php doc states:
"__call() is triggered when invoking inaccessible methods in an object context."
We also tested it in 7.3.10-nts-Win32-VC15-x64, 5.6.40-nts-Win32-VC11-x86 and 8.0.10-nts-Linux-x64.
Results are consistent.
Testscript is crunched down in lines. For a nice formatting and code comments please visit https://stackoverflow.com/questions/69161169/unexpected-behavior-when-utilizing-call-and-callstatic-from-different-scopes
I have read the threads with bug id 51176 & 77344
and you might argue that "that's not a static call operator, its a scope resolution
operator." But since 2008 calling non static members statically causes error messages. Further
more when you replace the calls on the non existing members with an forward_static_call, which
literally is documented with "Call a static method", it results in the same behavior.
Reproducible with replacing the calls
A::BLA(), B::BLA() with forward_static_call(array("A", "BLA")),
forward_static_call(array("B", "BLA"));
I understand the behavior daniel described in bug.php?id=51176 [2013-08-17 13:59 UTC] daniel dot
ruthardt at zoesolutions dot eu, but either the __call magic function catches the wrong call or
forward_static_call should be rather called forward_scope_resolution_operator_call. Ive got the
feeling here that there is not a consent about how the :: operator should actually work.
Thanks in advance and lovely greetings from Germany
Test script:
---------------
class B {
public function __call($name, $arguments) {print(" __CALL on B\r\n");}
public static function __callStatic($name, $arguments) {print(" __STATICCALL on
B\r\n");}
}
class A {
public function __call($name, $arguments) {print(" __CALL on A\r\n");}
public static function __callStatic($name, $arguments) {print(" __STATICCALL on
A\r\n");}
public static function doMagicCall_from_staticContext() {
A::BLA(); // expect A::__callStatic, works
B::BLA(); // expect B::__callStatic, works
}
public function doMagicCall_from_objectContext() {
A::BLA(); // expect A::__callStatic, got $this->__call(), prints "__CALL on A"
B::BLA(); // expect B::__callStatic, works
}
}
$a = new A();
print("1. Call from static context:\r\n");
A::doMagicCall_from_staticContext();
print("\r\n2. Call from object context:\r\n");
$a->doMagicCall_from_objectContext(); // provokes the behavior
Expected result:
----------------
1. Call from static context:
__STATICCALL on A
__STATICCALL on B
2. Call from object context:
__STATICCALL on A
__STATICCALL on B
Actual result:
--------------
1. Call from static context:
__STATICCALL on A
__STATICCALL on B
2. Call from object context:
__CALL on A
__STATICCALL on B
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=81436&edit=1