DWS WP Framework
  • Welcome
  • Primary goals
    • Modular design
    • No 3rd-party dependencies
  • Key concepts and dev tools
    • PHP and WP requirements
    • Object-Oriented Programming
    • Semantic Versioning
    • Version Control (git / GitHub)
    • Dependency Management (Composer)
    • Automated Testing (Codeception + Github Actions)
    • Dependency Injection (PHP-DI)
    • Coding Standards (PHPCS and PHPMD)
    • Dependencies Scoping (PHP-Scoper)
    • TypeScript and Sass
    • Task Runners (Grunt)
  • Setting up your dev environment
    • Windows
  • Your first plugin
    • Multiple plugins using the framework on the same site
  • Frequently Asked Questions
  • Bootstrapper Module
    • Motivation
    • How it works
    • How to use
    • White Labeling
  • Helpers Module
    • Motivation
    • How to use
  • Foundations Module
    • Motivation and How to use
    • Actions
      • Local action traits
      • Extension action traits
      • Integration action traits
    • States
    • Utilities
      • Stores
      • Handlers and Services
        • Logging Service
  • Plugin
    • Main Plugin Instance
    • Plugin Components
  • Hierarchies
  • Helpers
  • Utilities Module
    • Motivation and How to use
    • Hooks Service
      • Scoped Handler
    • Shortcodes Service
    • Templating Service
    • Assets Service
      • Scripts Handler
      • Styles Handler
    • CRON Events Service
      • Action Scheduler Handler
    • Admin Notices Service
    • Dependencies Service
    • Validation Service
  • Core Module
    • Motivation and How to use
    • Plugin Tree
      • Plugin Root
      • Plugin Functionality
    • Plugin Components
      • Internationalization
      • Installation / Upgrade / Uninstallation
  • Settings Module
    • Motivation and How to use
    • Settings Service
      • WordPress Handler
      • MetaBox Handler
      • ACF Handler
    • Validated Settings
  • WooCommerce Module
    • Motivation and How to use
    • Extended WC Logger
    • WC Settings Handler
Powered by GitBook
On this page

Was this helpful?

Hierarchies

PreviousPlugin ComponentsNextHelpers

Last updated 4 years ago

Was this helpful?

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 .

This document is not meant to replace a basic understanding of or . 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 interface (obligatory trait) and the interface (obligatory 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 interface. A basic implementation is provided by the 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 , that's where those interfaces are explained.

Therefore, two new abstract classes arise: the class and the class which extend the and classes, respectively.

All the concepts presented in 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 and for both and . You can find them all in their respective folders inside , but here is a selection:

  • -- the current instance is active only if its direct parent is active too.

  • -- if the current instance's direct parent is disabled, it is disabled too

  • -- upon successful initialization, initialize the instance's children too.

  • -- after setting up, set up all active and not disabled children too.

Here is a very basic example of a plugin tree:

<?php

namespace DeepWebSolutions\Plugins\MyTestPlugin;

use DeepWebSolutions\Framework\Foundations\Actions\InitializableInterface;
use DeepWebSolutions\Framework\Foundations\Actions\SetupableInterface;
use DeepWebSolutions\Framework\Foundations\Hierarchy\Plugin\AbstractPluginRoot;
use DeepWebSolutions\Framework\Foundations\Hierarchy\Plugin\AbstractPluginNode;
use DeepWebSolutions\Framework\Foundations\Hierarchy\Actions\InitializeChildrenTrait;
use DeepWebSolutions\Framework\Foundations\Hierarchy\Actions\MaybeSetupChildrenTrait;
use DeepWebSolutions\Framework\Foundations\Hierarchy\States\ActiveParentTrait;
use DeepWebSolutions\Framework\Foundations\Hierarchy\States\DisabledParentTrait;
use DeepWebSolutions\Framework\Foundations\States\ActiveableInterface;
use DeepWebSolutions\Framework\Foundations\States\DisableableInterface;
// Exceptions import and Local traits import ommitted for brevity

class MyPlugin extends AbstractPluginRoot implements InitializableInterface, SetupableInterface {
    use InitializeChildrenTrait;
    use MaybeSetupChildrenTrait;
    use SetupLocalTrait;
    use SetupOnInitializationTrait;
    
    public function get_plugin_file_path(): string {
        return $path_to_file_with_plugin_header_comment;
    }
    
    protected function setup_local(): ?SetupFailureException {
        // do some local setup ... like registering some actions and filters
        return null;
    }
}

class GenericComponents extends AbstractPluginNode implements InitializableInterface, SetupableInterface, ActiveableInterface, DisableableInterface {
    use InitializeChildrenTrait;
    use InitializeLocalTrait;
    use MaybeSetupChildrenTrait;
    use SetupLocalTrait;
    use ActiveLocalTrait;
    use ActiveParentTrait;
    use DisabledLocalTrait;
    use DisabledParentTrait;
    
    protected function is_active_local(): bool {
        // add your own logic for this node (and all of its descendants) to be active
        // for example, a setting in the WP admin area
        return true;
    }
    
    protected function is_disabled_local(): bool {
        // add your own logic for this node (and all of its descendants) to be disabled
        // for example, this plugin branch could be for compatibility with another plugin
        // and thus, if that plugin is not active, nothing here should run
        return false;
    }
    
    protected function initialize_local(): ?InitializationFailureException {
        // do some local initialization, basically just making sure that
        // everything is in order for the 'setup_local' call
        return null;
    }
    
    protected function setup_local(): ?SetupFailureException {
        // do some local setup, like registering actions and filters,
        // registering shortcodes, setting up REST routes,
        // adding admin notices, whatever
        return null;
    }
}

$plugin     = new MyPlugin();
$component1 = new GenericComponent();
$component2 = new GenericComponent();
$component3 = new GenericComponent();

$plugin->add_child( $component1 );
$component1->add_child( $component2 );
$component1->add_child( $component3 );

// automagically run everything!
$plugin->initialize();

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):

$plugin->initialize();
$plugin->initialize_local(); // see AbstractPlugin class in the Plugin namespace
$plugin->integrate_initialize_children();

$component1->initialize();
$component1->initialize_local();
$component1->integrate_initialize_children();
$component2->initialize();
$component2->initialize_local();
$component3->initialize();
$component3->initialize_local();

// full plugin tree has been initialized successfully

$plugin->setup(); // setup method called automagically because of the action integration trait
$plugin->setup_local();
$plugin->integrate_maybe_setup_children();

$component1->is_disabled();
$component1->is_disabled_local();
$component1->is_active();
$component1->is_active_local();
$component1->setup();
$component1->setup_local();
$component1->integrate_maybe_setup_children();

$component2->is_disabled();
$component2->is_disabled_local();
$component1->is_disabled(); // because of the parent checking trait
$component2->is_active();
$component2->is_active_local();
$component1->is_active(); // because of the parent checking trait
$component2->setup();
$component2->setup_local();

$component3->is_disabled();
$component3->is_disabled_local();
$component1->is_disabled();
$component3->is_active();
$component3->is_active_local();
$component1->is_active();
$component3->setup();
$component3->setup_local();

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.

here
trees
linked lists
ParentInterface
ParentTrait
ChildInterface
ChildTrait
NodeInterface
NodeTrait
the Plugin section
AbstractPluginRoot
AbstractPluginNode
AbstractPlugin
AbstractPluginComponent
the Plugin section
extension traits
integration traits
actions
states
the Hierarchy namespace on GitHub
ActiveParentTrait
DisabledParentTrait
InitializeChildrenTrait
MaybeSetupChildrenTrait
Plugin tree structure