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

php.internals

하늘아부지

2 years ago
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 <?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 ```

Claude Pache

2 years ago
> 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 > <?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: ```php 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`: ```php 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

Stéphane Hulard

2 years ago
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 > > <?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: >
> ```php >
> 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`: >
> ```php > 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 <?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

하늘아부지

2 years ago
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 > > > <?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: > > > > > ```php > > > > > 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`: > > > > > ```php > > 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 > <?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