Roots Discourse

Composer-managed plugin installed via composer

I see two posts that describe an issue similar to mine:

  1. Plugin: install dependencies / dump autoload
  2. Global autoload

Neither contains either a solid reference or a solution.

I learned that:

  1. Plugin’s composer.json needs to define it as "type": "wordpress-plugin" if you want it to be loaded into site/web/app/plugins (or it ends up in site/vendor.)

Still struggling with:

  1. The rest of the "require" dependencies are being put in site/vendor, unless they also have "type": "wordpress-plugin" defined in their composer.json, in which case they are installed as separate plugins.

  2. No dependencies are installed inside of myplugin/vendor. (They’re all in site/vendor or web/app/plugins.)

Here’s a MWE of the plugin’s composer.json:

{
    "name": "mikeill/my-plugin",
  	"type": "wordpress-plugin",
    "keywords": ["wordpress", "plugin"],
    "require": {
        "php": ">=7.0",
        "composer\/installers": "1.x",
        "johnbillion\/extended-cpts": "4.3.*",
        "cmb2\/cmb2": "2.7.*",
    },
    "require-dev": {
        "codeception\/codeception": "4.1.*",
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "autoload": {
        "psr-4": {
            "My_Plugin\\Backend\\": "backend",
            "My_Plugin\\Frontend\\": "frontend",
        },
        "files": [
            "vendor\/wp-custom-bulk-actions\/custom-bulk-actions.php"
        ]
    },
    "extra": {
        "installer-paths": {
            "vendor\/{$name}\/": [
                "cmb2\/cmb2",
                "seravo\/wp-custom-bulk-actions"
            ]
        }
    }
}

I could, I believe, add a task to build-before.yml (trellis/site/deploy-hooks):

  - name: Install Composer dependencies for my plugin
   command: composer install --no-ansi --no-dev --no-interaction --no-progress --optimize-autoloader --no-scripts --classmap-authoritative
   args:
     chdir: "{{ deploy_helper.new_release_path }}/web/app/plugins/my-plugin"

But that would still leave a bunch of duplicate files in site/vendor as well as duplicate plugins.

Is there an appropriate approach and/or an example of how this should be handled?

Why is this a problem?

Well, @allwaysblank, thank you, as usual for your input.

It seems like something is wrong if the dependencies from the plugin are residing simultaneously in two places: site/vendor and app/plugins/my-plugin/vendor.

On a practical level, the autoload_real files composer is trying to require aren’t all there. For example:

  • /srv/www/ellipticastudios.com/current/vendor/composer/autoload_real.php is trying to find
  • vendor/wp-custom-bulk-actions/custom-bulk-actions.php
  • which is located in plugins/my-plugin/vendor
    "autoload": {
            "psr-4": {
                "My_Plugin\\Backend\\": "backend",
                "My_Plugin\\Frontend\\": "frontend"
            },
            "files": [
                "vendor\/wp-custom-bulk-actions\/custom-bulk-actions.php"
            ]
        }

It’s a little difficult to debug this w/o knowing a little more about what your plugin does and how you intend to distribute it. If you intended to distribute this plugin generally (i.e. it’s not for a specific project/your internal use) you may run into some difficulties w/ packaging and playing nice w/ Bedrock that I don’t know if I can help you with).

A plugin installed through composer is just a composer package installed to a different location. In the context of Bedrock, the code that the plugin executes at runtime will have access to all the dependencies install by Bedrock’s composer.json, and you should write your plugin code accordingly.

You’re correct that this is wrong: They should all be in site/vendor. Your plugin should not have its own vendor folder and composer context (or if it must you can’t/shouldn’t install it w/ Bedrock’s composer.json).

Your plugin’s composer.json is trying to manually load the wp-custom-bulk-actions file but doesn’t ever define wp-custom-bulk-actions as a dependency so I’m not sure how you expect that file to be there. Additionally, if wp-custom-bulk-actions is defined as a dependency (i.e. https://github.com/Seravo/wp-custom-bulk-actions), then it will be loaded and available to your plugin automatically–no need to manually load the file.

It’s not clear to me why you want the non-standard file organization you’re describing above.

1 Like

Let’s ignore that for now as I don’t actually need Custom Bulk Actions, which was included in the plugin boiler plate template I’m trying.

In other words, if I’m using a plugin that defines composer dependencies, Composer should install them in site/vendor as opposed to within their individual directories?

Is that also what happens with Sage’s composer dependencies?

Yes.

Not by default, no.

Sage and Bedrock are built as separate products–they work well together but neither requires the other. In theory I believe you could move all of the composer stuff Sage depends on (autoloading, dependencies, etc) up to Bedrock–I do this w/ dependencies–but this would fuse your Bedrock and Sage instances together. For the sites I’m usually building, this is fine–they’re not meant to ever be distributed separately–but it’s something to keep in mind depending on future use cases for your theme. That’s why the suggested build-before tasks in Trellis run composer install for Sage on deploy.

You could, in theory, treat your plugins the same way: Keep each one isolated and include a composer install task for each plugin in your deploy setup. I wouldn’t recommend this, because it gets complicated quickly and increases the likelihood of two separate composer installs installing the same package and then exploding at runtime because things are trying to redefine functions or what have you.

The composer paradigm (and the Roots paradigm, insofar as we can cram it into WordPress) is about treating your site as a single app, and everything else as a dependency for that app. In Roots this means that WordPress, plugins, and composer packages are dependencies–conceptually it’s pretty “flat.” Unfortunately WordPress has a different conception of what things are, what they mean, and where they go, and that makes it hard to line up the paradigms perfectly.

Yes. It is being annoying and I’m not necessarily planning to use this plugin elsewhere. So I have added !web/app/plugins/myplugin to git ignore and am including as part of the single Trellis/Bedrock app.

So in the plugin where I call on the dependencies:

$my_on_demand_libraries = require_once MY_PLUGIN_ROOT . 'vendor/autoload.php';

Would you update MY_PLUGIN_ROOT to instead point to whatever bedrock defines current/site as? (If you know off-hand what that is, I’m poking around for it now.)

You shouldn’t need to do this. If you’ve defined your dependencies in Bedrock they should already be available when your plugin loads.

I have moved the composer requirements from the plugin’s own composer.json file into site/composer.json, including the psr4 autoloads which I am currently pointing into the plugin

    "autoload": {
        "psr-4": {
            "My_Plugin\\Backend\\": "web/app/plugins/my-plugin-dir/backend",
            "My_Plugin\\Frontend\\": "web/app/plugins/my-plugin-dir/frontend"
        }
    },

I see how composer knows which dependencies to put where:

"extra": {
    "installer-paths": {
      "web/app/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
      "web/app/plugins/{$name}/": ["type:wordpress-plugin"],
      "web/app/themes/{$name}/": ["type:wordpress-theme"]
    },

However:

Plugin functions don’t run unless I initialize the plugin:

$prefix    = $this->composer->getPrefixesPsr4();
$classmap  = $this->composer->getClassMap();

($this->composer is an instance of Composer\Autoload\ClassLoader, as returned by getLoader())

Confounding:

  • Requiring
    • '/srv/www/example.com/current/vendor/autoload.php', returns (bool) true, while
    • '/srv/www/example.com/current/web/app/plugin/myplugin/vendor/autoload.php' returns the Composer\Autoload\ClassLoader instance I (think I) want.

Wondering if I should be bypassing the “initialization” altogether, as I believe you suggest above, and not sure how to do that.

Your continued attention is much appreciated, man.

Currently requireing the Composer\Autoload\ClassLoader object (as opposed to require_once) in the plugin, because of the way the plugin is setup to “Initialize”. Using require_once, instead of returning the object, was returning boolean true, because that file has already been loaded (at the top of wp-config.). Thank you, SO.

This is, maybe, redundant. I’m thinking of refactoring the plugin into the Acorn framework.

For now, at least, I seem to be up and running again.

I don’t think that framework is meant to be installed with Composer; I think it just uses Composer to manage its own dependencies. Its readme doesn’t mention Composer as a viable install process: https://github.com/WPBP/WordPress-Plugin-Boilerplate-Powered/blob/75fdefab7064f525a0e0faf8c0bd19fa50f4d910/plugin-name/README.txt#L45-L71

1 Like

Thanks. At this point I have it working with the codebase as part of the Bedrock app, managing the plugins dependencies via site/composer.php. Since it’s setup to require the Composer\Autoload\ClassLoader, I’m requiring that a second time from within the plugin itself and it seems to be working.

As mentioned above, I may migrate to Acorn, but I discovered some new dependencies, like codeception, that I’m glad to now be aware of.