[PHP-DEV] [RFC idea introduce] The issue where the __callStatic magic method is not called when statically calling a public method.

Hi.

I would like to register an RFC on the following issue.
https://github.com/php/php-src/issues/13813

To summarize briefly, users expect that the __callStatic magic method will be called in all cases when a method is not static while using the __callStatic. However, in reality, if the method is public, the __callStatic magic method is not called, and an error still occurs.

I would like to hear your opinions.
I also hope someone can help with the RFC registration.


<?php
class MyClass
{
public static function __callStatic($method, $args)
{
echo $method . "\n";
}

private function privateMethod() {}
protected function protectedMethod() {}
public function publicMethod() {}
}

MyClass::privateMethod();
MyClass::protectedMethod();
MyClass::publicMethod();

Resulted in this output:

privateMethod
protectedMethod
Fatal error: Uncaught Error: Non-static method MyClass::publicMethod() cannot be called statically in ...

But I expected this output instead:

privateMethod
protectedMethod
publicMethod

Le 28 mars 2024 à 03:29, 하늘아부지 daddyofsky@gmail.com a écrit :

Hi.

I would like to register an RFC on the following issue.
https://github.com/php/php-src/issues/13813

To summarize briefly, users expect that the __callStatic magic method will be called in all cases when a method is not static while using the __callStatic. However, in reality, if the method is public, the __callStatic magic method is not called, and an error still occurs.

I would like to hear your opinions.
I also hope someone can help with the RFC registration.


<?php
class MyClass
{
public static function __callStatic($method, $args)
{
echo $method . "\n";
}

private function privateMethod() {}
protected function protectedMethod() {}
public function publicMethod() {}
}

MyClass::privateMethod();
MyClass::protectedMethod();
MyClass::publicMethod();

Resulted in this output:

privateMethod
protectedMethod
Fatal error: Uncaught Error: Non-static method MyClass::publicMethod() cannot be called statically in ...

But I expected this output instead:

privateMethod
protectedMethod
publicMethod

Hi,

One of the issue is that it is not possible to determine statically that a certain call is static or not. It is possible to determine it at runtime; but you must be careful to articulate clearly the rules. Although it is absolutely possible to have logical rules, I fear that they couldn’t be sufficiently simple in order to avoid confusion.

In the following real-world (but hopefully rare) case (it is taken from my own codebase), I am calling a method of a grandparent class. It is not a static method:


foo = new class(/* ... */) extends Bar {

// ...

function Header() {
$grandparent_class = get_parent_class(parent::class);
$grandparent_class::Header();
// ... rest of code
}

// ...
};

In the following more general case, I might intend to call to a static method. However, as of today, a call to a non-static method will occur if A has a non-static accessible method qux() and $this is an instance of A:

class Foo {
function baz() {
A::qux();
}
}

(In older versions of PHP, a non-static call would occur even when $this is not an instance of A. Hopefully, this is no longer the case since PHP 8.)

—Claude

Le jeudi 28 mars 2024 à 10:40, Claude Pache <claude.pache@gmail.com> a écrit :

> Le 28 mars 2024 à 03:29, 하늘아부지 <daddyofsky@gmail.com> a écrit :
>

>

> Hi.
>

> I would like to register an RFC on the following issue.
> The functionality of the `__callStatic` magic method is incomplete. · Issue #13813 · php/php-src · GitHub
>

> To summarize briefly, users expect that the `__callStatic` magic method will be called in all cases when a method is not static while using the `__callStatic`. However, in reality, if the method is public, the `__callStatic` magic method is not called, and an error still occurs.
>

> I would like to hear your opinions.
> I also hope someone can help with the RFC registration.
>

>

> --------------------------------------------------------
>

> ```php
> <?php
> class MyClass
> {
> public static function __callStatic($method, $args)
> {
> echo $method . "\n";
> }
>

> private function privateMethod() {}
> protected function protectedMethod() {}
> public function publicMethod() {}
> }
>

> MyClass::privateMethod();
> MyClass::protectedMethod();
> MyClass::publicMethod();
> ```
>

> Resulted in this output:
> ```
> privateMethod
> protectedMethod
> Fatal error: Uncaught Error: Non-static method MyClass::publicMethod() cannot be called statically in ...
> ```
>

> But I expected this output instead:
> ```
> privateMethod
> protectedMethod
> publicMethod
> ```
>

>

Hi,
One of the issue is that it is not possible to determine statically that a certain call is static or not. It is possible to determine it at runtime; but you must be careful to articulate clearly the rules. Although it is absolutely possible to have logical rules, I fear that they couldn’t be sufficiently simple in order to avoid confusion.

In the following real-world (but hopefully rare) case (it is taken from my own codebase), I am calling a method of a grandparent class. It is *not* a static method:


foo = new class(/* ... */) extends Bar {

    // ...

    function Header() {
        $grandparent_class = get_parent_class(parent::class);
        $grandparent_class::Header();
       // ... rest of code
    }

   // ...
};

In the following more general case, I might intend to call to a static method. However, as of today, a call to a non-static method will occur if `A` has a non-static accessible method `qux()` *and* `$this` is an instance of `A`:

class Foo {
    function baz() {
        A::qux();
    }
}

(In older versions of PHP, a non-static call would occur even when `$this` is not an instance of `A`. Hopefully, this is no longer the case since PHP 8.)

—Claude

Hello !

In Laravel the static versus non static context is handled inside the different calls. So a static call on a non static method will be forwarded to a non static instance to be handled properly.
I don't see any advantages to call `__callStatic` on any method call since the difference with `__call` is clear. Also, as Claude said, determining that the call is static or not is not easy.

Also, building Singleton is possible using the current PHP behavior. An example :

<?php

class User
{
    public function __callStatic($name, $arguments)
    {
        return (new static)->$name(...$arguments);
    }

    public static function __call($name, $arguments)
    {
        // Here handle the call the $name method.
        echo $name;
    }
}

$user = User::find(1);
//Will output
//   find

That's the way it's done in Laravel. Also note that the find method doesn't exists in the User object. That's why it works, the method is truly "magic".
If the method is defined as non static, it'll break with the error given.

Stéphane

2024년 3월 28일 (목) 오후 6:33, Stéphane Hulard <s.hulard@chstudio.fr>님이 작성:

Le jeudi 28 mars 2024 à 10:40, Claude Pache <claude.pache@gmail.com> a écrit :

Le 28 mars 2024 à 03:29, 하늘아부지 <daddyofsky@gmail.com> a écrit :

Hi.

I would like to register an RFC on the following issue.
https://github.com/php/php-src/issues/13813

To summarize briefly, users expect that the __callStatic magic method will be called in all cases when a method is not static while using the __callStatic. However, in reality, if the method is public, the __callStatic magic method is not called, and an error still occurs.

I would like to hear your opinions.
I also hope someone can help with the RFC registration.


<?php
class MyClass
{
public static function __callStatic($method, $args)
{
echo $method . "\n";
}

private function privateMethod() {}
protected function protectedMethod() {}
public function publicMethod() {}
}

MyClass::privateMethod();
MyClass::protectedMethod();
MyClass::publicMethod();


Resulted in this output:

privateMethod
protectedMethod
Fatal error: Uncaught Error: Non-static method MyClass::publicMethod() cannot be called statically in ...

But I expected this output instead:

privateMethod
protectedMethod
publicMethod

Hi,
One of the issue is that it is not possible to determine statically that a certain call is static or not. It is possible to determine it at runtime; but you must be careful to articulate clearly the rules. Although it is absolutely possible to have logical rules, I fear that they couldn’t be sufficiently simple in order to avoid confusion.

In the following real-world (but hopefully rare) case (it is taken from my own codebase), I am calling a method of a grandparent class. It is not a static method:


foo = new class(/* … */) extends Bar {

// …

function Header() {
$grandparent_class = get_parent_class(parent::class);
$grandparent_class::Header();
// … rest of code
}

// …
};


In the following more general case, I might intend to call to a static method. However, as of today, a call to a non-static method will occur if A has a non-static accessible method qux() and $this is an instance of A:

class Foo {
function baz() {
A::qux();
}
}

(In older versions of PHP, a non-static call would occur even when $this is not an instance of A. Hopefully, this is no longer the case since PHP 8.)

—Claude

Hello !

In Laravel the static versus non static context is handled inside the different calls. So a static call on a non static method will be forwarded to a non static instance to be handled properly.
I don’t see any advantages to call __callStatic on any method call since the difference with __call is clear. Also, as Claude said, determining that the call is static or not is not easy.

Also, building Singleton is possible using the current PHP behavior. An example :

<?php

class User
{
public function __callStatic($name, $arguments)
{
return (new static)->$name(...$arguments);
}

public static function __call($name, $arguments)
{
// Here handle the call the $name method.
echo $name;
}
}

$user = User::find(1);
//Will output
// find

That’s the way it’s done in Laravel. Also note that the find method doesn’t exists in the User object. That’s why it works, the method is truly “magic”.
If the method is defined as non static, it’ll break with the error given.

Stéphane

Hi.

I tested what Claude pointed out.
https://3v4l.org/qb6HH

From the test results and comments by iluuu1994 on GitHub ((https://github.com/php/php-src/issues/13813#issuecomment-2021625413), it seems that visibility is checked first, followed by whether it’s static or not. I wonder if swapping these checks might solve the issue, but it seems like this part will have to be left to the experts.

I understand what Stéphane pointed out. The part I’m interested in is whether it’s possible to use singletons easily without separating the class. __call alone cannot solve this part.

For example, in Laravel, methods defined in the DB class can be called through __callStatic in the Model class, but methods defined in the Model class or classes inheriting from the Model class cannot be called using ::. Therefore, scope-related methods must be implemented using a scope prefix. You’re using scopeKeyword methods like User::keyword. I think this awkward usage and the grotesque form that arose because __callStatic is not called in the case of all public methods.

daddyofsky