How to achieve the same result?
In PHP, you just call any function by name / any method by name / create a class by name:
$f_name = "...";
$f_name();
$f_name(1, "with", ["arguments"]);
// and methods
$method_name = "...";
A::$method_name();
call_user_func(['A', $method_name]);
// and classes
$class_name = "...";
new $class_name("constructor", "parameters");
This is prohibited in KPHP. All calls, all property access — everything must be statically resolved.
Think of KPHP as of any other compiled language. For dynamic variables, nothing better than switch-case actually exists.
And this is OK, this is not a drawback. Static call graph makes your code predictable and understandable, you always see all available methods reached out from here.
$func_name = "...";
switch ($func_name) {
case "authenticate":
authenticate();
break;
case "logout":
logout();
break;
}
If $class_name
is compile-time known, you are able to create it via new $class_name
or call like $class_name::method()
:
/**
* @kphp-generic T
* @param class-string<T> $class_name
*/
function demo($class_name) {
$obj = new $class_name; // ok
$obj->afterCreated();
$class_name::staticMethod(); // also ok
}
demo(A::class); // actually, demo<A>('A')
demo(B::class); // actually, demo<B>('B')
It works only for compile-time known variables, in generic functions. It can work, because everything is still statically resolved.
Probably, when you are searching for a way to “create any class” or “call any function”, that “any” comes from an input. Hence, generics will not fit your needs.
Let's say you want your-site.com?page=profile&action=edit to call \UrlHandler\Profile::edit()
. You have various classes in \UrlHandler namespace for &page get parameter and every class has a method for &action.
First of all, even in plain PHP, it's a bad idea to do like this:
// bad: primitive url mapping
$class_name = "\\UrlHandler\\" . ucfirst($_GET['page']);
$method_name = $_GET['action'];
$class_name::$method_name();
Why is it a bad pattern? Take these points under consideration:
“You handle this in each handler” using PHP code is not a good practice — not just because it leads to code duplication — but because you spread the area of responsibility between routing and business logic.
Declarative descriptions are much acceptable than imperative handling.
Having all this, you probably already use a declarative manner:
Route::get('/profile/edit', ['logged' => true], 'Profile::edit');
class Profile {
/**
* @url-handler logged:true method:get
*/
static public function edit() { /* ... */ }
}
[
{ "url": "/profile_edit", "logged": true, "method": "get", "handler": "Profile::edit" }
]
All these approaches are similar for the following reasons:
Because of (2), you can write a script that generates a switch-case code — and use it instead of (3).