Hierarchies
One thing that shows up often in Computer Science are graphs. It should be no surprise then that we make use of them too! The relevant namespace is called Hierarchy and the files are found here.
This document is not meant to replace a basic understanding of trees or linked lists. If those concepts are unfamiliar to you, you should first brush up on those topics before reading the rest of this page.
The two key concepts of a hierarchy are the so-called parents and the so-called children. The foundations module offers abstractions for children that have 0-or-1 parents and for parents that have 0-or-more children. Those are the ParentInterface
interface (obligatory ParentTrait
trait) and the ChildInterface
interface (obligatory ChildTrait
trait).
Because of the one parent constraint, the resulting graph will always be a tree where the only node without a parent is the so-called root.
More interesting, however, are objects that can be both a parent and a child. Those are called nodes and are modelled by the NodeInterface
interface. A basic implementation is provided by the NodeTrait
trait. The only noteworthy addition is the ability to query an instance's depth within the tree. The root has depth 0, its children have depth 1, its grandchildren have depth 2, and so on...
So far, these abstractions have universal applications. You can use them worry-free for anything that can be modelled as a tree. For example, you could build an abstract model of a table where the root is an instance of an imaginary Table
class and the children are instances of an imaginary Column
class.
However, the main reason why these abstractions were created was to offer the ability to create a plugin tree with a PluginInterface
-implementing object as the root and PluginComponentInterface
-implementing objects as the rest of the nodes. If you've skipped the Plugin section, that's where those interfaces are explained.
Therefore, two new abstract classes arise: the AbstractPluginRoot
class and the AbstractPluginNode
class which extend the AbstractPlugin
and AbstractPluginComponent
classes, respectively.
All the concepts presented in the Plugin section still apply. The only difference is that we now have a logical connection between all the individual plugin components. The root is at the top and everything else belongs somewhere in the abstract plugin tree structure. Getters like get_plugin
now default to a simple tree traversal algorithm for retrieving the root.
The introduction of hierarchical plugin components also brings new extension traits and integration traits for both actions and states. You can find them all in their respective folders inside the Hierarchy namespace on GitHub, but here is a selection:
ActiveParentTrait
-- the current instance is active only if its direct parent is active too.DisabledParentTrait
-- if the current instance's direct parent is disabled, it is disabled tooInitializeChildrenTrait
-- upon successful initialization, initialize the instance's children too.MaybeSetupChildrenTrait
-- after setting up, set up all active and not disabled children too.
Here is a very basic example of a plugin tree:
In case you're still confused about what happens in the example above, here is the plugin tree structure:
... and here is the full call stack trace, spelled out, after calling initialize
(noop method calls ommitted for brevity):
You read that right. All of the above happens automagically in the background upon calling $plugin->initialize()
thanks to the traits we included. The call stack gets short-circuited if a local action method returns a corresponding exception or if a local state method returns the opposite boolean value of what we pre-configured in the example above.
Congratulations! If you understood the example above, then you understand exactly how to use the Foundations Module to build your own plugins -- just mix-and-match action and state traits in your plugin components to achieve the desired results. Or just skip it all and use the pre-built logic provided by the Core Module.
Last updated