16 November 2018 —
A couple years ago I wrote a post about how to improve PHP applications performance by using zend-servicemanager
lazy services.
In that article I explained how the ServiceManager
takes advantage of the proxy design pattern to delay the creation of services, when they are marked as lazy.
That can improve performance if the object is resource consuming, but that is not the only advantage behind proxies.
A use case
Some days ago I was working on a new feature for Shlink, an open source project I maintain.
The feature consisted on adding support to geolocate IP addresses by using GeoLite2, an IP address database which is updated every month.
They have an official PHP library, which exposes a GeoIp2\Database\Reader
service where you need to inject the path to the GeoLite2 library file.
Since Shlink uses the zend-servicemanager
to manage dependency injection, I created a factory like this (these examples are pseudocode):
And then registered it like this:
Then I registered other services which were depending on this one, and everything seemed to be ok.
The issue
My intention was to download the GeoLite2 database during Shlink’s installation.
The problem is that the GeoIp2\Database\Reader
service throws an exception if the file you specified does not exist, and the first time Shlink is executed, it does not exist.
The way I designed my service graph was making the GeoIp2\Database\Reader
object to be instantiated, and throw an error before being able to download the file.
I started to think on ways to workaround this issue, but none of them seemed clean enough.
Then I thought “maybe I can wrap the object somehow, so that it is not created until it is going to be actually used”, and then I realized that was exactly what a Proxy does, and also, that the ServiceManager
includes its own implementation to do this.
The solution
The solution was pretty straightforward. I just had to mark the service as lazy, changing the configuration to this:
And that’s it. No further code changes were needed, only configuration.
With this change, the constructor of the GeoIp2\Database\Reader
is no longer run until the object is going to be actually used, which allows Shlink to be installed, and the database file to be downloaded.
If you want to more deeply understand what the new config actually means, it is explained in detail in my previous article about lazy services.
Conclusion
With this article I just wanted to share how to solve a specific issue introduced by third party code, where you don’t have the “power” to change the implementation.
I also wanted to expose a real use case, which will probably remind you to some real situation in which you have faced a similar problem in the past.