Managing plugins

My application isn’t for my own site, it’s an open source app for others to use. As such, there are a lot of optional features that may or may not be enabled, or where you pick one of a few options.

Online payment provider is what I’m currently looking at, but it’s not the only example. I have two providers supported at the moment, and a third one in progress. To date, I’ve just had a drop-down to select which one to use, and a class for each one, with a consistent interface to work through. That has been okay, but I’m finding it in terms of how the third one wants to work.

I’m thinking that I should move these into plugins, where they’d have a little more freedom to do what they need to do. But if I’m doing that, what I’d like is a structure where I don’t have to change anything about existing code when a new plugin is added, the system would somehow be able to recognize that it’s there, know what type it is, and add it to the list of options in the appropriate place. And ideally, it would be able to somehow handle third-party plugins dropped into the vendor folder via composer or whatever.

I’m not anticipating any problem with implementing whatever structure I come up with, but very much unsure about what that structure should be. Maybe I need a plugins table that has a manually-created record for each one (perhaps automatically created by migrations in the plugins themselves; just using manual as an alternative to, for example, scanning the file system).

So, if you’ve got any good ideas for me along these lines, pointers to where others have done this sort of thing, anything like that, I’m happy to entertain any possibility.

1 Like

I don’t think a plugins table is a great idea. I would use a registry (api)

In you app you would define a “PaymentRegistry” (see TransportRegistry for example) and/or PaymentInterface, every plugin registers its classes in that registry.

// plugin/FooPayment/src/Plugin.php
public function bootstrap()
   PaymentRegistry::load('Foo', Configure::read('Foo'));

// src/Application.php
public function bootstrap()
   // ...
   $this->loadPlugin('FooPayment'); // cake calls FooPayment/Plugin::bootstrap()

(I did not test this, it should work with a few adjustments)

You would simply loop over PaymentRegistry::loaded() to get all active Payment classes

Just to try and get your basic problem clear in my mind:

You are creating an open source app that developers would configure from a set of optional modules that you would make available as guaranteed-to-be-supported.

But the developers might also configure in some other modules that they would compose in (or otherwise include). These you would want your system to recognize as an alternate to one of the supported modules. Then your system would call this interloper instead of your out-of-the box choices.

Is this the general idea?

If this is all more or less on the mark, I have another question.

It sounds like you may have various categories of swap-in modules. You mention online payment as your current variant.

If there will be more than one category then the developer would have to tell your app what category of module they were providing, correct?

Okay, but how does my Application even know what plugins to be loading? If I’ve got 20 payment providers, I don’t want to load all of them, just the one that is in use.

Yes, that’s the general gist of it. In a “standard” application, you have a list of plugins from various places that you load to do various things. I’m basically considering how to make that list not only select which plugins to use dynamically, but to be self-configurable to some extent by the plugins themselves.

Yes, that’s correct. It’s even theoretically possible that one plugin might provide multiple types of functionality (like how a single PHP class may implement multiple interfaces), though I can easily jettison that concept if it proves too cumbersome.

If you don’t want to add 1 line per plugin, you can setup something like package discovery with composer api. Using the extra key in composer.json (like laravel package autodiscovery).

I see. And to further understand how you are structuring things (not yet addressing the issue of run-time assembly of all developer configuration choices).

Given that your design must address 3 degrees of developer freedom

  • Select a module from a list of supported modules OR
  • Install a foreign module that is compatible with the interface for that module type so the system automatically detect and uses the module OR
  • Allow the developer a way to install a foreign module that is not compatible but provide them a way to integrate their choice into the system so it will be detected and used like any other module of that type.

Is it correct that you’re application will be structured something like this:

To state this in another way:

A layer class (like a controller say) allows the developer to choose a service provider for some supported type of service (online payment for example). And to support a variety of different solutions:

  • The application anticipates a certain interface will exist for the type of module.
  • A concrete provider is selected by the developer and a corresponding adapter would be loaded to make the concrete choice’s interface match the applications defined interface expectations.

This pattern would satisfy all three degrees of freedom you want for developers.

  • Adapters would exist for all supported modules or those modules might need no adapters.
  • The user could configure a new concrete choice if an adapter existed that could translate the interface (or if it already matched the apps expectations)
  • The user could configure a new concrete choice and provide the adapter to make it work.

If this is correct, I’ll try and get closer to an actual suggestion to your question :slight_smile:

1 Like

I’m not sure if the adapter layer would be useful enough to spend the time to accommodate, maybe just the concrete implementations. Unless you mean that the implementation might in turn be based on some package from yet another vendor? I don’t really draw a distinction between those cases; in either case, there’s a plugin that interacts with my system, and I don’t much care how it goes about implementing its functionality.

And the end target isn’t necessarily developers, but whoever wants to run the application; third-party developers might build plugins that somebody installs into their system. Maybe analogous to something like WordPress, but the plugin installation doesn’t need to be nearly that seamless just yet. I think a composer require would be a reasonably easy bar to hit without a lot of effort.

Ok. I’m thinking about things differently than you. I will ponder your question and comments again.

Maybe something like I use for my UserPrefs system would serve your needs.

I use a very thin table with one ‘data’ field and one linking field

CREATE TABLE `preferences` (
  `prefs` text COMMENT 'Make this a json type field',
  `user_id` int(11) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)

At run-time, once I can identify the user that is acting (or in your case some expected system configuration?) I load the proper prefs record.

This gives me an open ended amount of data in a flexible structure that can grow as needed.

You might store a plugins node on it that lists the plugins and their configurations. Or you might even store a full Registry object on it in a hybrid solution with @raul338.

I regularly store objects rather than arrays in sessions and you should be able to do the same with a column like this.

My code can grow by looking at the data for any new node I decide I need and dealing with the data there or a null. Old established nodes are unaffected by changes.

On a side note to the ‘prefs’ approach:

Either the entire field content or individual nodes can be sent to Modelless Form classes for processing. This gives you a single field that can contain any number of discrete schemas and all the advantages of Validation, Rules and what-not. Or at the very least discrete and well defined Form to manage the stored data.

It is even trivial to take the Cakey-conventions approach of naming each node in the preferences.prefs column to identify the Form Class that should handle its contents.

Managing preferences is straight-forward. I’m still stuck on how to even find the list of plugins that I might present for selection. In WordPress, if I upload a new folder into the plugins folder, WordPress finds it right away, no extra work required. And I could do that easily enough if I knew that plugins was the only place to look. But composer installs third-party stuff into the vendor folder and there’s a ton of stuff already in there. Do I periodically recursively scan that folder looking for yaml files that contain content specific to my application and add them to the list?

Alright! This, is a problem of uncontrolled installations then. Solving it by trying to code for every possible present and future pattern seems very… difficult.

An alternative is to try an make some single ‘installation’ control point (a console command ) that is flexible enough to allow un-modified pass-through of various installation requests while also giving you a place to capture (or be told) the essential identity and nature of the package being installed.

Your command could support passing an argument like “require robmorgan/phinx” and a flag that indicated this should be passed on as a composer installation.

Then the command code would know that something was being installed, the name of the thing, and what technique was being used to install it. The command would then be able to focus its deductive logic on a know situation.

If you found there were still questions who’s answers couldn’t be guessed in some installation scenarios, you could add some required arguments so the installing actor must provide the data you can’t guess.

This command then would give you a control-point to capture the knowledge rather than requiring your program know how to discover everything from a complex and hodgepodge of files.

1 Like

Weird that it only just told me today that this post was here…

At the moment, I’ve added a plugins table to remember what it knows about, and uses that to drive what gets loaded, as well as a controller to show the list of known plugins and allow them to be enabled / disabled / configured. Each plugin will be required to have a YAML file that includes things like author, web site, description.

I like the idea of having a command that can add to that table, and will look into how best to hook into composer functionality, to let plugins somehow call this command as they’re being required, or vice versa. May also build a “scan for new plugins” function to let it all happen in the UI.

Thanks for the suggestions!