Sort all loaded plugins using a plugin dependency topology

Execution order matters: if plugin A depends on plugin B, it usually means that plugin B does stuff which plugin A requires. However, Pico loads plugins in alphabetical order, so events might get fired on plugin A before plugin B.

Hence plugins need to be sorted. Pico sorts plugins using a dependency topology, this means that it moves all plugins, on which a plugin depends, in front of that plugin. The order isn't touched apart from that, so they are still sorted alphabetically, as long as this doesn't interfere with the dependency topology. Circular dependencies are being ignored; their behavior is undefiend. Missing dependencies are being ignored until you try to enable the dependant plugin.

This method bases on [Marc J. Schmidt's Topological Sort library](https://github.com/marcj/topsort.php) in version 1.1.0, licensed under the MIT license. It uses the `ArraySort` implementation ([class `\MJS\TopSort\Implementations\ArraySort`](https://github.com/marcj/topsort.php/blob/1.1.0/src/Implementations/ArraySort.php)).
This commit is contained in:
Daniel Rudolf 2016-12-06 17:18:59 +01:00
parent 0a269746eb
commit 7a6e4f8271
No known key found for this signature in database
GPG key ID: A061F02CD8DE4538

View file

@ -300,6 +300,7 @@ class Pico
// load plugins // load plugins
$this->loadPlugins(); $this->loadPlugins();
$this->sortPlugins();
$this->triggerEvent('onPluginsLoaded', array(&$this->plugins)); $this->triggerEvent('onPluginsLoaded', array(&$this->plugins));
// load config // load config
@ -491,6 +492,72 @@ class Pico
return $plugin; return $plugin;
} }
/**
* Sorts all loaded plugins using a plugin dependency topology
*
* Execution order matters: if plugin A depends on plugin B, it usually
* means that plugin B does stuff which plugin A requires. However, Pico
* loads plugins in alphabetical order, so events might get fired on
* plugin A before plugin B.
*
* Hence plugins need to be sorted. Pico sorts plugins using a dependency
* topology, this means that it moves all plugins, on which a plugin
* depends, in front of that plugin. The order isn't touched apart from
* that, so they are still sorted alphabetically, as long as this doesn't
* interfere with the dependency topology. Circular dependencies are being
* ignored; their behavior is undefiend. Missing dependencies are being
* ignored until you try to enable the dependant plugin.
*
* This method bases on Marc J. Schmidt's Topological Sort library in
* version 1.1.0, licensed under the MIT license. It uses the `ArraySort`
* implementation (class `\MJS\TopSort\Implementations\ArraySort`).
*
* @see Pico::loadPlugins()
* @see Pico::getPlugins()
* @see https://github.com/marcj/topsort.php
* Marc J. Schmidt's Topological Sort / Dependency resolver in PHP
* @see https://github.com/marcj/topsort.php/blob/1.1.0/src/Implementations/ArraySort.php
* \MJS\TopSort\Implementations\ArraySort class
* @return void
*/
protected function sortPlugins()
{
$plugins = $this->plugins;
$sortedPlugins = array();
$visitedPlugins = array();
$visitPlugin = function ($plugin) use ($plugins, &$sortedPlugins, &$visitedPlugins, &$visitPlugin) {
$pluginName = get_class($plugin);
// skip already visited plugins and ignore circular dependencies
if (!isset($visitedPlugins[$pluginName])) {
$visitedPlugins[$pluginName] = true;
$dependencies = array();
if ($plugin instanceof PicoPluginInterface) {
$dependencies = $plugin->getDependencies();
} else {
$dependencies = array('PicoDeprecated');
}
foreach ($dependencies as $dependency) {
// ignore missing dependencies
// this is only a problem when the user tries to enable this plugin
if (isset($plugins[$dependency])) {
$visitPlugin($plugins[$dependency]);
}
}
$sortedPlugins[$pluginName] = $plugin;
}
};
foreach ($this->plugins as $plugin) {
$visitPlugin($plugin);
}
$this->plugins = $sortedPlugins;
}
/** /**
* Returns the instance of a named plugin * Returns the instance of a named plugin
* *