This piece was contributed by guest author Peter Suhm. Peter is a web developer from the Land of the Danes. He is the creator of WP Pusher and a huge travel addict, bringing his work along with him as he goes.
The other day I posted a warning about using Composer in WordPress plugins on the WP Pusher blog. This post got a lot of attention and I feel the need to clarify a few points that were not all clear to everyone. The article was also a bit heavy on the technical stuff, so in this post I will try to make my main point more clear by using a simple narrative to illustrate it.
Let’s imagine for a while that you and I are both plugin authors. Both of us have a great idea for a plugin we wish to distribute via WordPress.org. We want to include a few premium features in our plugins that users of the free version can unlock by entering a license key.
We need some code that can handle this process. Both of us realize that this problem have probably already been solved by someone else. None of us are fans of reinventing the wheel, so we head over to Packagist and type in “license manager”. It looks like our assumption was justified. Yoast already has a package that can handle this. We both decide to do a quick
composer require yoast/license-manager. Easy peasy. Now we can move on to work on something that really matters- the core features of our respective plugins.
Fast forward, ready to release your plugin, you realize something: Your user doesn’t necessarily have Composer handy when installing your plugin from WordPress.org, so how are they going to get the code for the license manager? This situation is a bit annoying, because the only solution you really see is to just commit the whole Composer generated
vendor directory to your plugin and push it to WordPress.org. You know this is not how Composer is supposed to work, but whatever. You don’t really have other options.
Meanwhile, I have come to the same conclusion with my plugin. Just include the license manager code and be done with it.
Fast forward once more, both our plugins now live in the WordPress.org repository and once in a while, someone decides to upgrade to our premium versions. Everything seems to be fine and we are both grateful that we could just use the code that Yoast had generously open sourced, and didn’t have to reinvent the wheel.
One day, you receive a strange e-mail. A customer is experiencing some really strange behavior when trying to unlock your premium features. It makes no sense to you, because no one else ever reported this. After hours of debugging, you finally ask your customer to deactivate everything else, except your plugin, and then: It works! Hmm. Your plugin seems to somehow be incompatible with another plugin. My plugin.
You realize this after hours of going through source code of all the other plugins the customer had installed. When you realize that we both use the license manager, a bell rings. Could this really be it? If so, how come no
fatal errors: cannot redeclare class was caused by PHP?
A week earlier, I had bumped the required version of the license manager in my plugin to the latest version, which included some (fictional) breaking changes. After even more debugging and
var_dump()’ing, you realize that my version of the license manager is also the version loaded by PHP in your plugin. You find that really strange because you specifically required another version of the license manager with Composer. You don’t really know what to do about this.
Because there really isn’t much you can do about it.
What happened here?
Now that we have all seen the problem, let’s take a moment to go through what actually happened in the narrative. First of all, why didn’t PHP cause a fatal error when two classes obviously had the same name that both of us included the license manager?
The reason for this is that we used an autoloader generated by Composer. This autoloader scans the diretory structure of our dependencies and adds every class to the autoloader. If a class has already been added, Composer will ignore it. Silently. I have written a small code example if you want to see it for yourself. It’s on GitHub.
Why was my version of the license manager included before yours?
This happened because my plugin had a name that caused it to be loaded before yours. Maybe, in the future, we will all name our plugins “Aaaaaa My Plugin” in order to be loaded first!
So to sum up, the main issue here is that we won’t know which version of our dependencies are available to us at which time. It simply depends on factors we can’t fully control as plugin developers.
Is this a Composer specific issue?
No. It really isn’t. WordPress doesn’t have a way of dealing with third party code in plugins or themes. Therein lies the problem. The reason why I’m talking about Composer is that it is gaining a lot of traction these days. If WordPress developers want to use Composer in plugins released via WordPress.org, this needs to be solved somehow. Otherwise, we will see true chaos when all plugins starts to be incompatible with each other because they use different versions. Welcome to debugging hell.
What can we do about this?
Someone who has been really concerned about this and has worked hard to find a potential solution is Coen Jacobs. I decided to reach out to Coen and ask him if he thinks there is anything we can do about this.
Many developers are already including 3rd party code in their plugins. Is this really a problem?
Yes, this is already a problem in the plugins ecosystem. It will become even worse when more people figure out it’s a good idea to put common functionality in separate packages. These packages can then be bundled with multiple plugins and the issue will appear more and more. I’ve been speaking to a couple developers who have already been through debug hell trying to find out what’s causing this issue.
Moving forward, would you suggest developers stop including 3rd party code in their plugins?
I’m a bit torn on this subject. It makes no sense from a developers point of view to tell people to stop bundling shared packages in their plugins. On the other hand, everybody wants the best possible user experience for their users. It’s a tough decision to make for sure.
At this point, I want to push WordPress related development forward. I want to share libraries and use libraries shared by others. Nobody should be reinventing the wheel over and over again. So I would take the risk of running into issues like this, solving the problems as they show up.
This also means that I’ll be doing my damned best to find a long term solution for this issue. More people will start using Composer, more people will bundle libraries with their plugins. This problem will show up more often, so it’s time to fix it.
What can plugin developers do to prevent this problem?
There is a workaround that I have seen some people use already. It basically comes down to moving your dependency to the namespace of your plugin. Danny van Kooten did this for one of his plugins. This is not ideal however. Every time he updates his dependencies, he has to go through all the files and change the namespaces again. Now this is not such a big task for a relatively small library like Pimple, but a massive undertaking for larger libraries.
This can only be done with namespaces though, so you’ll have to make your plugin require PHP 5.3+ as well. I’m not gonna lie, I think every plugin should start doing that sooner or later, but it’s definitely something you need to consider when you decide to do this.
What would the ideal solution be, if there is any?
The ideal situation would be using some sort of dependency manager. There is of course Composer, the most used dependency manager. Composer is very hard, if not impossible, to use for the vast majority of the WordPress users. It’s a developer’s tool after all.
WordPress should make this easier for its end users, while still enabling developers to utilize pretty much any package they want. On this thought, I have started putting together the WordPress Composer Installer plugin, which does all the hard Composer work while people install plugins as they always have. As soon as I am been able to finish this up, I’ll integrate it properly into the whole plugin installer flow.
Now maybe one day, this can be integrated in core WordPress. It has a long way to go, but the proof of concept already works.
If you have been reading this far, first of all: Thank you. Second of all, I hope you now see how this is something that will eventually become a problem. Our current situation is very frustrating, because we simply don’t have the tools we need. Still, I think it’s important that we keep talking about this and make sure that we all, as WordPress developers, understand the potential issues caused by conflicting third party dependencies in our code.
Finally, I want to mention one more time that this is not a Composer issue. It’s a WordPress issue.