Introduction
In your PHP application, you may sometimes see dynamic method calls being used. This is typically where a method name is constructed at runtime and then called on an object. For example, $this->{'methodName'}()
could be used to call a method named methodName
.
Dynamic method calls can be useful, but they also come with some risks that you should be aware of.
In this article, we'll explore the dangers of using dynamic method calls in PHP and provide some alternatives to consider.
What are Dynamic Method Calls?
Dynamic method calls in PHP allow you to call a method on an object using a variable or expression to determine the method name at runtime.
Typically, you'd call a method using something like $this->methodName()
. But with dynamic method calls, you can do something like $this->{'methodName'}()
or $this->{$variable}()
where $variable
contains the name of the method you want to call.
As you can imagine, this can be quite powerful, as it allows for flexible code. This is especially true in scenarios where the method to be called is not known until runtime (such as if you're building a framework or library). And I think this is where they can be useful. However, in most application code, I think there are usually better, safer alternative approaches.
To help us understand the dangers, let's take a look at an example. We'll imagine we're building a class which handles a webhook we receive from an external service. The webhook payload contains an event
field which tells us what type of event it is (e.g. "success", "failure", etc.). We want to call a different method based on the value of this event
field. For instance, if the event is "success", we want to call a handleSuccessWebhook
method. If it's "failure", we want to call a handleFailureWebhook
method. The payload might look something like this:
1$payload = [2 'event' => 'success',3 'details' => [4 // ...5 ],6];
And the webhook handler class might look something like this:
1final readonly class WebhookHandler 2{ 3 public function handleWebhook(array $payload): void 4 { 5 $this->{'handle' . ucfirst($payload['event']) . 'Webhook'}($payload); 6 } 7 8 private function handleSuccessWebhook(array $payload): void 9 {10 // Handle a "success" webhook here...11 }12 13 private function handleFailureWebhook(array $payload): void14 {15 // Handle a "failure" webhook here...16 }17 18 // Other webhook handling methods here...19}
We can see here that we have a public handleWebhook
method which takes the payload as an argument. It extracts the event
field from the payload and then constructs the method name to call based on that event. It uses ucfirst
to ensure the first letter of the event is capitalised, and then appends "Webhook" to the end of it. Finally, it calls the constructed method name on $this
.
The Dangers of Dynamic Method Calls
Now let's explore some of the dangers of using this approach.
Difficulty with IDEs
One of the biggest issues I've faced with dynamic method calls is that integrated development environments (IDEs), such as PhpStorm, struggle to understand their usage. Since the method name is being constructed at runtime, it's difficult for the IDE to detect whether the handleSuccessWebhook
and handleFailureWebhook
methods are actually being used. This can lead to the IDE marking them as unused, which can be misleading.
In the past, I've been tempted to delete methods that my IDE has marked as unused, only to later discover that they were indeed being used via a dynamic method call. This can lead to bugs and unexpected behaviour in your application. Thankfully, I caught them in time before deploying to production. But it was a close call.
Another downside to PhpStorm not being able to understand the dynamic method calls is that you can't make full use of the IDE's refactoring tools. For example, if you want to rename a method in PhpStorm, it won't be able to find all the references (since they're dynamic), and it won't rename them for you. This can lead to broken code if you're not careful.
Harder to Find
Another issue I've found with dynamic method calls is that you can't easily search for their usages in your codebase.
Let's say you want to find anywhere the handleSuccessWebhook
method is being called. So you hit CMD+SHIFT+F in PhpStorm to open the global search window and search for "handleSuccessWebhook"
. You won't find any results (apart from the method definition itself), because the method name is never explicitly mentioned anywhere else in the code.
At this point, you've got to ask yourself: "Is this method being used anywhere? Or is it safe to delete it?". This question becomes much easier to answer if you have a comprehensive test suite that covers that particular method and confirms that it is being used. But if your test suite doesn't cover this particular feature, then you've got to manually inspect the code to see if it's being used anywhere. This can be time-consuming and error-prone, especially in larger codebases.
Harder to Read
I personally find that dynamic method calls can make the code harder to read at first glance. Which of these looks clearer to you at first glance?
1// Dynamic method call:2$this->{'handle' . ucfirst($payload['event']) . 'Webhook'}($payload);3 4// Or, traditional method call:5$this->handleSuccessWebhook($payload);
I'm going to make a guess that most people would find the traditional method call clearer. With the dynamic method call, you've got to mentally parse the string concatenation to figure out what method is being called. This can be especially challenging if the string concatenation is more complex, or if you don't know what the possible values of $event
are.
An Alternative Approach
For the reasons above, I generally avoid using dynamic method calls in my code. Instead, I prefer to use a more explicit approach.
This is purely my personal preference, though, and it's not to say that dynamic method calls are inherently bad. So if you're reading this and use them in your own code, please don't think I'm insulting your code. If it works for your use case, is fully covered by tests, and makes you productive, then I'm all for it.
I just prefer the extra confidence and safety I get from using a more explicit approach. And I also think it makes the code easier to read, especially for developers who are new to the codebase.
If I were to refactor the WebhookHandler
class above, I might use a match
expression instead:
1final readonly class WebhookHandler 2{ 3 public function handleWebhook(array $payload): void 4 { 5 match ($payload['event']) { 6 'success' => $this->handleSuccessWebhook($payload), 7 'failure' => $this->handleFailureWebhook($payload), 8 default => throw new Exception('No handler for the event: ' . $event) 9 };10 }11 12 private function handleSuccessWebhook(array $payload): void13 {14 // Handle a "success" webhook here...15 }16 17 private function handleFailureWebhook(array $payload): void18 {19 // Handle a "failure" webhook here...20 }21 22 // Other webhook handling methods here...23}
In the approach above, we've used a "match expression" which reads the payload's event
field and then calls the appropriate method based on its value. If the event is not recognised, it throws an exception.
By using this approach, we've been able to explicitly write the method names in our code. As a result, it means our IDE can understand their usage, so we can make full use of its features (such as refactoring tools, and knowing which types will be returned). It also means we can easily search for their usages in our codebase. I'd also argue that it makes the code easier to read at a first glance, too.
Conclusion
Hopefully, this article has given you some food for thought about the dangers of using dynamic method calls in PHP. While they can be useful in certain scenarios, they also come with some risks that you should be aware of.
If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
Or, you might want to check out my other 440+ page ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀