https://stackoverflow.com/questions/10053479/a-php-plugin-architecture
https://roots.io/routing-wp-requests/
https://stackoverflow.com/questions/42/best-way-to-allow-plugins-for-a-php-application
Foundation for create Object Oriented Framework
http://agile-core.readthedocs.io/en/develop/hook.html
http://agile-core.readthedocs.io/en/develop/index.html
https://torquemag.io/2017/09/embracing-the-microservice-architecture-pattern-with-wordpress/
https://stackoverflow.com/questions/3356376/php-creating-extensible-cms-system/3356922#3356922
https://martinfowler.com/articles/201701-event-driven.html
https://martinfowler.com/eaaCatalog/plugin.html
https://roots.io/routing-wp-requests/
https://stackoverflow.com/questions/42/best-way-to-allow-plugins-for-a-php-application
Foundation for create Object Oriented Framework
http://agile-core.readthedocs.io/en/develop/hook.html
http://agile-core.readthedocs.io/en/develop/index.html
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:
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
You could use an Observer pattern. A simple functional way to accomplish this:
<?php
/** Plugin system **/
$listeners = array();
/* Create an entry point for plugins */
function hook() {
global $listeners;
$num_args = func_num_args();
$args = func_get_args();
if($num_args < 2)
trigger_error("Insufficient arguments", E_USER_ERROR);
// Hook name should always be first argument
$hook_name = array_shift($args);
if(!isset($listeners[$hook_name]))
return; // No plugins have registered this hook
foreach($listeners[$hook_name] as $func) {
$args = $func($args);
}
return $args;
}
/* Attach a function to a hook */
function add_listener($hook, $function_name) {
global $listeners;
$listeners[$hook][] = $function_name;
}
/////////////////////////
/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');
function my_plugin_func1($args) {
return array(4, 5);
}
function my_plugin_func2($args) {
return str_replace('sample', 'CRAZY', $args[0]);
}
/////////////////////////
/** Sample Application **/
$a = 1;
$b = 2;
list($a, $b) = hook('a_b', $a, $b);
$str = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";
$str = hook('str', $str);
echo $str;
?>
Output:
This is my CRAZY application
4 + 5 = 9
4 * 5 = 20
Notes:
For this example source code, you must declare all your plugins before the actual source code that you want to be extendable. I've included an example of how to handle single or multiple values being passed to the plugin. The hardest part of this is writing the actual documentation which lists what arguments get passed to each hook.
This is just one method of accomplishing a plugin system in PHP. There are better alternatives, I suggest you check out the WordPress Documentation for more information.
Sorry, it appears underscore characters are replaced by HTML entities by Markdown? I can re-post this code when this bug gets fixed.
Edit: Nevermind, it only appears that way when you are editing
https://stackoverflow.com/questions/3356376/php-creating-extensible-cms-system/3356922#3356922
https://martinfowler.com/articles/201701-event-driven.html
https://martinfowler.com/eaaCatalog/plugin.html
Comments
Post a Comment