Proper package management is an essential part of dependency management and stable plugin development in WordPress. This article gives an introduction to package management in WordPress, describes related problems and points out possible approaches to solutions.
Package management is the ability to install re-usable work distributed as a package and to maintain correct relationships between those packages at different versions. In the scope of packages necessary for a single project, it is often referred to as Dependency Management. A package, such as a plugin or a code library, may depend on multiple other packages. And those packages may, in turn, have dependencies of their own, and so on. Moreover, those dependencies are often constrained to specific version ranges.
Determining and installing the correct versions of all dependencies of a project recursively is the purpose of a dependency management system. Another important feature of package management systems is the ability to determine whether a recursive set of requirements can be resolved to an installable list of packages, i.e. that all requirements can be satisfied. And warning the user if they cannot while providing some information that would help resolve the conflict.
Packages can be as big or as small as necessary in order to contain a logical solution. This promotes the granularity of solutions and allows them to focus on more narrow problems. With this approach, fewer assumptions need to be made, less conditions satisfied, the packages become lighter and more versatile. With the separation of concerns, interoperability also increases.
Modularity in WordPress
Although WordPress exposes the same API to all extensions, there is no unified package format. Instead, WordPress extensions have the form of either plugin or theme. Furthermore, plugins may be must-use, and are installed in a different way to regular plugins; and themes may extend other themes, making them child themes. Finally, there are dropins, which are neither plugins nor themes but provide yet another way of customizing WP behavior by outright replacing certain aspects of it. Perhaps due to the above, there is no unified way of installing or distributing extensions. There is no notion of “package”, and therefore no package management is available.
Moreover, WordPress itself does not provide package metadata, relies heavily on globals, and makes many assumptions about the environment, thus making it extremely difficult to distribute or depend on as a package. Several solutions for installing WordPress as a Composer package exist, such as johnpbloch/wordpress-core-installer.
Although WordPress has no unified concept of modules, it is still possible to make cross-platform modules and use them in WordPress plugins or themes. However, this is not in the scope of this article.
The Need for Dependency Management
Although WordPress provides its own, albeit ambiguous, modularity system, there is a need for unified dependency management. This is because extensions often need to re-use existing solutions for common problems. The open-source community provides a wealth of libraries and standards, which would drastically decrease development time and complexity: either by providing algorithms that solve specific programming problems or by allowing substitution of software components without breaking the software. In the overwhelming majority of cases, these solutions are distributed in the form of Composer packages. WordPress extension authors may therefore depend on these packages, and focus on the specific tasks that their extension aims to solve, rather than on facilitating their solutions. It would also allow extension authors to collaborate on packages in the open-source space, which would yield endless benefits for all: developers would waste less time, and users would get a more solid, feature-rich, and bug-free experience.
At the time of writing, many extension authors, such as Inpsyde and RebelCode, have already embraced this philosophy, which allowed them to approach their work more seriously, introduce more testing, automation, speed up and distribute development. Solutions like WP Starter and Bedrock exist to manage whole WordPress environments via Composer and other virtualization tools, which is paramount for enterprises and agencies.
Problems with Dependency Management in WordPress
- WordPress has no dependency management mechanism.
- Extension authors want to use dependency management.
- Extension authors are therefore forced to ship dependencies together with their products.
- This means that dependencies of different extensions are not centralized, causing extension size to grow.
- This also means that dependencies of different extensions have a considerable chance to conflict.
- Good solutions are granular, but the higher the granularity, the more packages are required. And hence the higher the chance of conflict.
- On the other hand, using popular solutions means less installed dependencies, but the chance of conflict still grows due to more extensions shipping the same packages.
The reason for conflicts is that two different extensions may ship different versions of the same package they depend on. Only one version will be used by all dependent extensions due to autoloading and the nature of FQCNs, but there is no way of controlling which version gets used. Furthermore, if the different versions are incompatible (either because they have a different API, or because their implementations are substantially different), it will cause one of the extensions to break, regardless of which version is used. This problem is well described by Peter Suhm, who has developed a number of tools to help with the concern.
Possible Solutions for conflicting extensions
Several approaches exist to tackle the conflict problem. Please note that even in the ideal scenario, it may not be possible to always keep all products compatible. Some programs are just incompatible due to conflicting purposes or other reasons. And this is a fact of reality which there is nothing to be done about.
Most likely, the ultimate solution is a combination of several or all of the approaches outlined below.
Introduce Dependency Management to WordPress
This would be the ideal solution. Alas, it’s nowhere near. There are tools that make updating and CI/CD easier, such as WP Pusher or Branch. But nothing integrates Composer into WordPress. When the benefits of Composer became apparent, Rarst had suggested that a machine-readable package description is added to the Core. At the time of writing this, 6 years had passed since then. Thus, this is not a viable approach currently, and may never be.
Put WordPress under Composer
Basically, this means using WordPress in a Composer-managed environment as one of the dependencies of that environment. WordPress extensions may also be declared as dependencies in the same way, and oomphinc/composer-installers-extender provides a way to place each extension type into the correct folder, while johnpbloch/wordpress-core-installer or similar would make sure that WordPress core itself is where it needs to be.
The problem of this approach is that it requires the WordPress environment to already be managed by Composer. This is taxing on the server in terms of software required to be installed on it. And it is not something a regular WordPress user can do. Nonetheless, it is currently a good and acceptable solution for websites that are entirely managed by an agency.
Upgrade Plugin Dependencies Simultaneously
This means simply making sure that all the plugins which we want to keep compatible have compatible dependencies. It requires simultaneous maintenance and release of those plugins and is extremely difficult to achieve. If one plugin requires a newer BC-breaking version of a dependency, the other plugin would have to somehow follow. And it becomes more and more difficult, the more plugins need to keep compatible with each other.
Centralize Dependencies in a Framework Plugin
The opposite approach to the previous. It requires selecting a very commonly used code and shipping it as a separate plugin, which other plugins would then depend on. WordPress does not have a native mechanism of declaring and signaling plugin inter-dependencies, but third-party solutions such as TGMPA are available, which provide users with the UI to help them be informed of and to satisfy these dependencies.
This approach requires serious commitment and diligence but is viable. Dependent plugins may suffer from varying degrees of decreased flexibility in which dependencies and their versions they must have. But the commitment to consistency may also be a benefit. However, there are marketing considerations related to the necessity of installing additional plugins. While most users are very familiar and comfortable with plugin installation, dependency on other plugins is not a common approach and may be somewhat confusing.
Automatic Refactoring During Build
This approach involves monkey-patching all code used in a project such that it uses only FQNs specific of that project. As a result, FQN conflicts will be avoided, because dependencies will have their namespaces changed. This approach has unpredictable consequences, and poses a huge interoperability concern, as it makes it impossible to standardize or re-use other standardized code. It may be a viable solution for cases where all code is maintained by the authors of the plugin, and where interoperability is not a concern. Tools such as Mozart and PHP-Scoper are available to automate this task, as it is not feasible without automation.
The interoperability problems become less severe if the tool of choice allows excluding certain namespaces from being prefixed. In this case, it could be possible to avoid prefixing interop interfaces, while still scoping the rest of the project-specific implementations. This would marginally increase the viability of this approach. Please note that at the time of writing, Mozart has not implemented a feature that would allow selected FQNs to be excluded from patching. For this reason, and because PHP-Scoper is a more generic, maintained, fully-featured, and widely adopted project, it should probably be used in most cases.
It is also worth noting that such patching is not a development concern, and is not related to the code of the project. The source files of the project should use original symbols and FQNs. Patching should be done at build time using a build tool such as Phing. This kind of build tool is neither a dev nor production dependency, and therefore it should be installed with something like Phive to avoid many unnecessary and confusing steps and declarations.
Cross Fingers and Hope For the Best
Depending on which dependencies are in scope and the number of target plugins, the issues may never present – especially when depending on extremely stable APIs. As such, dependencies containing standard interfaces like PSRs and similar have a very small chance of causing problems, because although they are and should be widely used, they change extremely rarely. Plugins that seldom get installed together with other possibly conflicting plugins, those that target enterprise environments, and short-term solutions like migration tools also have a lower risk of running into dependency incompatibility problems.