Bug->Doc #81436 [Fbk->Opn]: Unclear behaviors with __call/__callStatic/forward_static_call and LSB

From: Date: Thu, 16 Sep 2021 08:23:17 +0000
Subject: Bug->Doc #81436 [Fbk->Opn]: Unclear behaviors with __call/__callStatic/forward_static_call and LSB
References: 1  Groups: php.doc.bugs 
Request: Send a blank email to doc-bugs+get-19189@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
 Updated by:         requinix@php.net
 Reported by:        dev98 at outlook dot de
-Summary:            Unexpected behavior when utilizing __call and
                     __callStatic from different scope
+Summary:            Unclear behaviors with
                     __call/__callStatic/forward_static_call and LSB
-Status:             Feedback
+Status:             Open
-Type:               Bug
+Type:               Documentation Problem
-Package:            Scripting Engine problem
+Package:            Class/Object related
 Operating System:   Windows x64, Manjaro Linux x64
 PHP Version:        Irrelevant
 Block user comment: N
 Private report:     N

 New Comment:

> 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.

Sure.


Previous Comments:
------------------------------------------------------------------------
[2021-09-16 07:22:50] dev98 at outlook dot de

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.

------------------------------------------------------------------------
[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.

------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    https://bugs.php.net/bug.php?id=81436


--
Edit this bug report at https://bugs.php.net/bug.php?id=81436&edit=1


Thread (2 messages)

« previous php.doc.bugs (#19189) next »