Bug #81436 [Fbk]: Unexpected behavior when utilizing __call and __callStatic from different scope

From: Date: Tue, 14 Sep 2021 13:16:43 +0000
Subject: Bug #81436 [Fbk]: Unexpected behavior when utilizing __call and __callStatic from different scope
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-236591@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
 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:

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.


Previous Comments:
------------------------------------------------------------------------
[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


Thread (6 messages)

« previous php.bugs (#236591) next »