PHP RFC: Invoke __callStatic when non-static public methods are called statically
- Version: 0.9
- Date: 2014-03-29
- Author: Jaehan Seo, daddyofsky@gmail.com
- Status: Draft
- First Published at: http://wiki.php.net/rfc/complete_callstatc_magic
Introduction
The _ _callStatic magic method should be invoked when non-static public methods are called in a static context.
In PHP manual, _ _callStatic is described like below.
__callStatic() is triggered when invoking inaccessible methods in a static context.
In this context, interpreting inaccessible merely as invisible is insufficient because it omits whether the call is being made statically. It would be clearer to interpret it as not callable in a static context.
Non-static public methods are visible but cannot be called statically. Therefore, instead of throwing an error, the _ _callStatic method should be invoked when attempting to call a public method statically.
Here are examples of the current code and how the code would look if this RFC is accepted.
Case 1
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class User extends Model { public function scopeActive(Builder $query): void { $query->where('active', 1); } } $users = User::active()->get();
This is an example of a local scope using Laravel Eloquent ORM.
This code has the advantage of being able to categorize scope methods. However, the IDE cannot find active method, and the Go to Definition feature cannot be used.
Somewhere in the internal code not in _ _callStatic, there is code that matches the active method to the scopeActive method, but it's hidden, not easy to find.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public function active(): static { $this->query()->where('active', 1); return $this; } } $users = User::active()->get();
This code is very clear, aside from the fact that the method is not static.
The IDE recognizes active methods well and the Go to definition feature also works properly.
Case 2
Let's try to create a simple router class similar to the one in Laravel.
<?php class Router { public static __callStatic($method, $args) { return (new RealRouter())->$method(...$args); } } class RealRouter { public function prefix(): static { // some code return $this; } public function group(): static {} ... } Router::prefix()->group();
Even if there are only a few core methods, it cannot be made into a single file.
In this case as well, the IDE cannot find prefix method, and the Go to Definition feature cannot be used.
Certainly, you can add PHPDoc to the router class to make it recognize prefix methods, but still, you cannot directly move to the original method using the go to definition feature.
There is another problem: the result of calling a prefix method changes from the Router class to the RealRouter class. Of course, it can be adjusted in _ _callStatic, but it's confusing.
<?php class Router { public static __callStatic($method, $args) { return (new static())->$method(...$args); } public function prefix(): static { // some code return $this; } public function group() {} ... } Router::prefix()->group();
Aside from the fact that the method is not static, this code is very clear too.
Case 3
Let's make a custom facade in Laravel.
<?php // 1. app/Services/CustomService.php namespace App\Services; class CustomService { public function someMethod() {} }
<?php // 2. app/Facades/CustomFacade.php namespace App\Facades; use Illuminate\Support\Facades\Facade; class CustomFacade extends Facade { protected static function getFacadeAccessor() { return 'custom'; } }
<?php // 3. app/Providers/AppServiceProvider.php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Services\CustomService; class AppServiceProvider extends ServiceProvider { public function register() { $this->app->bind('custom', function () { return new CustomService(); }); } }
<?php use App\Facades\CustomFacade; CustomFacade::someMethod();
Now what if this RFC is accepted?
<?php // 1. app/Services/CustomService.php namespace App\Services; class CustomService { public static __callStatic($method, $args) { return (new static())->$method(...$args); } public function someMethod() {} }
<?php use App\Services\CustomService; CustomService::someMethod();
That's it.
When I raised this issue, many people said that the code would be unclear and obscure. But in reality, it's the opposite. As can be seen in the above examples, the code becomes clearer, and navigation through the IDE works much better.
Of course, calling non-static methods in a static-like manner can be confusing, but in modern PHP, it has already become common practice.
I believe this change allows for easier and more flexible use by users (anonymous programmers using the PHP language).
Proposal
The proposal is simple.
Instead of throwing an error when a non-static public method is called statically, the _ _callStatic method should be invoked.
<?php class MyClas { public static function __callStatic($method, $args) { echo '__callStatic : ' . $method . " is called statically.\n"; } public function nonStaticPublicMethod() {} } MyClass::nonStaticPublicMethod();
Before
Fatal error: Uncaught Error: Non-static method MyClass::publicMethod() cannot be called statically in ...
After
__callStatic : nonStaticPublicMethod is called statically.
Backward Incompatible Changes
None
Proposed PHP Version(s)
PHP 8.4
Open Issues
None
Patches and Tests
Not yet. Need volunteer.