Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A php plugin architecture

I'm trying to think of a way to create a plugin architecture for my own framework. I've read numerous topics and post here and on other sites. And basically i've came to the following solution which seems to be the only good option in PHP (currently).

The idea is that every class extends a sort of observer like class. So a Template class, BaseController etc. always extend a Plugin class.

class BaseController extends Plugin
{
    public function __construct()
    {
        // Plugin check, notify all loaded plugins
        $this->checkForEarlyHooks();

        // Init some standard stuff
        $this->view = new Template();
        $this->baseLayout = 'layout.html';

        $this->something = new Something();

        // Plugin check, notify all loaded plugins
        $this->checkForLateHooks();
    }
}

So what basically happends here is that when an indexController extends a baseController a plugin check is done. In this case for the constructor. This can be convenient when you want to do a some sort of login check with a plugin, before an Action method is actually invoked.

The Plugin class can resolve from what class is got called from and knows for what functions to look for in the loaded plugins.

Also note that it checks the loaded plugin list 2 times. One before anything is loaded (early) in the constructor, and one when all vars are loaded (late).

I can also add variables to the "checkForLateHooks()" function. So the hook functions can manipulate those too, like the 'baseLayout' variable.

A hook function would look like this:

public function hookConstruct ( &$baseLayout )
{
    $baseLayout = 'login.html';
}

Now basically my question is, is this approach any good? I know there are probably alot of other ways to do it too. But i mainly don't want to run into design problems later on. It seems like a good idea now, but you never know how things work out later on...

If i remember it right (from all the posts the i've read), this is kinda like WordPress does it (and some other frameworks).

like image 225
w00 Avatar asked Apr 07 '12 09:04

w00


2 Answers

UPDATE: answer now reflects the up-to-date links and better description.

There are certainly many different ways to design a plugin system and perhaps asking on https://softwareengineering.stackexchange.com/ would give you more ideas, but I'll try to help by sharing my ideas and experience.

I'll share some of my own experiences which I've learned through a series of my own frameworks. Currently Agile UI and Agile Data both support many wasy to be extended, but I'll focus on the "Components"

HOOKS

When you look to inject code into the existing object, a hook is a standard way to go. It's the best option for extending the application or flow with an established structure.

While refactoring my frameworks, I've separated hook implementation into a separate trait and documented it here: http://agile-core.readthedocs.io/en/develop/hook.html

Host application:

... some code here ..
$this->hook('beforeInit');
$this->init();
$this->hook('afterInit');
... code goes on ..

Plugin:

$host_app->addHook('beforeInit', function($object) {
    echo "About to execute init of $object";
});

UI COMPONENTS

Components present a different design pattern, which is suitable for the User Interfaces. You start with the page/app layout which is then broken down into Menu, Header, Footer, Content.

A component is an object which can be associated with Layout or the other component. Each component is capable of rendering and passing extra HTML/JS to its parent. Most of components would also be an interractive objects.

This approach is called "Render Tree" and application execution goes through 2 stages - "initialization of a render tree" and then "rendering".

Host Application:

$layout->menu = new \atk4\ui\Menu();
$layout->add($layout->menu, 'TopMenu');

The above code show how a new Component (Menu) can be initialized and inserted into the $layou. Furthermore the HTML output of $menu is directed into {$TopMenu} tag, which is defined in the HTML template of Layout.

Plug-in can interact with the render tree by:

  • adding more components anywhere in the tree
  • affecting existing components (e.g. add new menu item)
  • destroying any of the existing components

When those approaches are combined, you can use something like this:

$app->addHook('afterInitLayout', function($app) {

    $app->layout->menu->destroy(); // remove default menu
    $app->layout->menu = new \plugin\SuperMenu();
    $app->layout->add($app->layout->menu);
});

This can be used to replace standard menu with a more powerful implementation from your add-on.

My implementation of components is documented here:

http://agile-ui.readthedocs.io/en/latest/view.html#initializing-render-tree

UI / DATA SEPARATION

Although perhaps not so much answer for a question, but another powerful way to extending is a separation of concerns. All of the UI components in Agile UI do not know how to do anything with data.

While many UI generators require developer to manually build them up and link up with data, I am injecting "Model" object like this:

$form->setModel(new User($db)); // populates name, surname and gender fields

Demo: http://ui.agiletoolkit.org/demos/form2.php (2nd form)

In this approach object User contains enough meta-data for the Form to populate it's fields, captions perform validation and also save/load data.

Since "User" class can also be declared in an add-on it makes a pretty powerful way to extend existing functionality by adding support for new data entities.

OTHER APPROACHES

Some other approaches to extend with add-ons include:

Factory:

The ability to resolve class specified as a string into namespace / file:

$api->add('MyClass');

Gives ability for add-on to re-route standard classes but is not very friendly with type-hinting in IDEs.

New types / Traits:

Add-on can provide new classes adding Persistences, Table Columns, Form Fields, Actions etc.

CONCLUSION

I think add-on deign boils down to:

  • simplicity of installation and use
  • does add-on need to operate out-of-the-box?
  • avoiding conflicts between add-ons
  • defining different add-on types - authentication, UI, behaviour
  • can add-on extend another add-on
  • will add-on fit application design, data and architecture?
  • give a lot of power to add-ons without sacrificing performance
like image 60
romaninsh Avatar answered Oct 03 '22 22:10

romaninsh


Have a look at PHPPlugin on https://github.com/gheevel/PHPPlugin. A simple PHP plugin framework inspired by eclipse and jira plugin architectures. Basically, your application can define extension points and plugin instances can register on those extension points for providing extra functionality. Works well in combination with composer and/or symfony.

like image 36
gheevel Avatar answered Oct 03 '22 22:10

gheevel