5

In our plugin development we use Composer to install e.g. Symfony\Process that we later use in the code. The big question is how do we make sure that this dependency is not in a conflict with some other plugin also using Symfony\Process in another version.

Now I know this is mostly an inherent PHP issue but still, we'd like to offer our users a solution that at least warns them when such conflict happens (instead of failing hard). One approach, for example, would be to scan the plugins directory for vendor folders and try to figure out whether an incompatible versions are used in some other plugin. I know this far (like really, really far) from perfect but I'm just trying to give an example.

How do you guys deal with this? I know Composer is probably not mainstream amongst plugin developers yet but we all will have this problem sooner or later so I'm wondering if anyone has figured out some sort of strategy for it.

5

The answer is — you can't because WordPress can't. There is no dependency management concept in WP and it was the same case before Composer had even existed.

The rise of autoload (Composer or otherwise) slightly helps with it, since you might just be getting different copy loaded (hopefully close enough) rather than outright fatal error. You can check for this, if you devise a method to. For example checking the paths the definitions where loaded from with get_included_files(), but really that's just a bandaid not a solution.

The only scenario in which Composer really helps you with it is using it to control whole WP installation, with single shared vendor folder.

  • I think I understand this conceptually, the question is just what to do in a real-world scenario where a plugin that you're developing might take the site down in the case of an unfortunate combination with some other plugin. Would you just let it fail or would you do something against it? If so, what exactly? – Borek Bernard Jan 20 '15 at 12:45
  • Do best you can with a check (where best isn't much) or nothing. Most plugins around seem to just do nothing. Note that if it crashes on activation then WP won't turn it on. – Rarst Jan 20 '15 at 12:53
  • This really is an unpleasant issue but maybe it has no sensible solution due to how PHP versions classes. – Borek Bernard Jan 20 '15 at 13:10
3

Working on a classifieds CMS, heavily inspired by Wordpress (osclass.org not to name it), I stumbled upon your thread. We are in the exact same situation, having to deal with multiple plugins using the same dependency.

To explain the solution we've pulled out, let's we've got two plugins :

  • P1 uses dependency D1 in version 1.20
  • P2 uses dependency D1 in version 1.30

We have to make sure that version 1.30 is loaded for both plugins to work properly.

1. Plugins loading order

Wordpress (and Osclass) have a active_plugins option (in wp_options table) that defines active plugins and more importantly in our case : plugin loading order.

This is a simple array.

2. Version check

At the start of our two plugins, we check the version of D1 to be sure that the version is what we need (or superior).

// Custom autoloader, we'll check it in a moment.
require __DIR__ . "/vendor/composer_components/madhouse/autoloader/autoload.php";

if(!function_exists("mdh_utils") || (function_exists("mdh_utils") && strnatcmp(mdh_utils(), "1.30") === -1)) {
    // Dependency version is lower than 1.30 (which we need), let's bump.
    mdh_bump_me();
} else {
    // Rest of the plugin code.
}

Simple case: what happens when P2 is loaded before P1 ?

  • P2 is loaded and D1 with it. Note: loaded version of D1 is 1.30.
  • P1 is loaded next, checks if D1 is loaded and version is 1.20 or higher and find the version 1.30 then loads normally.

Everything's good.

Interesting case: What happens when P1 is loaded before P2 ?

  • P1 is loaded before P2, our dependency and our plugin P1 are loaded normally. Note: loaded version of D1 is 1.20.
  • P2 is loaded next, it checks D1 version and find out that D1 has been loaded but version is not high enough (1.20 instead of the required 1.30).

Here comes the bump version.

3. Bump !

Remember, plugins are loaded in the order defined in active_plugins.

The bump function – in this case mdh_bump_me() – will bump the current plugin to the top of the active_plugins (first position of the array).

Looks like this. This is Osclass-related code but it can easily be modified to work with Wordpress :

function mdh_bump_me()
{
    // Sanitize & get the {PLUGIN_NAME}/index.php.
    $path = str_replace(osc_plugins_path(), '', osc_plugin_path(__FILE__));

    if(osc_plugin_is_installed($path)) {
        // Get the active plugins.
        $plugins_list = unserialize(osc_active_plugins()); // This is the active_plugins fields unserialized.
        if(!is_array($plugins_list)) {
            return false;
        }

        // Remove $path from the active plugins list
        foreach($plugins_list as $k => $v) {
            if($v == $path) {
                unset($plugins_list[$k]);
            }
        }

        // Re-add the $path at the beginning of the active plugins.
        array_unshift($plugins_list, $path);

        // Serialize the new active_plugins list.
        osc_set_preference('active_plugins', serialize($plugins_list));

        if(Params::getParam("page") === "plugins" && Params::getParam("action") === "enable" && Params::getParam("plugin") === $path) {
            //osc_redirect_to(osc_admin_base_url(true) . "?page=plugins");
        } else {
            osc_redirect_to(osc_admin_base_url(true) . "?" . http_build_query(Params::getParamsAsArray("get")));
        }
    }
}

In a nutshell,

  • Get current active_plugins list.
  • Remove the current plugin from it.
  • Add the current plugin at the beginning of the array.
  • Save the option to the database.
  • Redirect to the same page.

5. Custom Autoloader

A word on our custom autoloader. Composer autoloader loads every class (even if it has been already defined), the new replacing the old one.

Our custom autoloader only loads a class if does not exist yet. Loads the first instance of each class and ignore every other ones.


Hope it's clear enough.

Far-far-far from an ideal solution but works for us and it feels like a step forward.

  • Thanks for a detailed answers. How do you deal with situations where two plugins depend on versions that are entirely incompatible, i.e., P1 in your example cannot work with v1.30? – Borek Bernard Oct 25 '15 at 10:06
  • And then every other plugin started doing this. The horror. – Jürgen Paul Dec 11 '18 at 10:06
  • Composer autoloader does not load a class if it already exists, but this depends on how you use it. If you use classmap or PSR autoload rules, then an existing class will not be loaded again. This is because the SPL autoload function that is in the base of this functionality is only triggered when you refer to an FQCN that does not exist. Of course, if you manually load specific paths, then this automatic functionality will not work correctly. – XedinUnknown Feb 27 at 10:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.