sehrgutesoftware/laravel5-api

View on GitHub
src/Laravel5_Api/PluginLoader.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace SehrGut\Laravel5_Api;

use InvalidArgumentException;
use SehrGut\Laravel5_Api\Hooks\Hook;
use SehrGut\Laravel5_Api\Plugins\Plugin;

/**
 * Takes care of Controller Plugins loading, calling, configuring….
 */
class PluginLoader
{
    /**
     * Instances of loaded plugins.
     *
     * @var array
     */
    protected $plugins = [];

    /**
     * Maps hooks to plugin classes. Don't add anything here manually!
     *
     * @var array
     */
    private $hooks = [];

    /**
     * The controller's current context.
     *
     * @var Context
     */
    private $context;

    /**
     * Construct a new instance.
     *
     * @param Controller $controller The principal controller
     * @param Context    $context    The controller's current context
     * @param array|null $plugins    List of plugin classes to load
     */
    public function __construct(Controller $controller, Context $context, array $plugins = null)
    {
        $this->context = $context;

        // Register controller hooks
        $this->loadPlugin($controller);

        // Load Plugins
        if ($plugins) {
            $this->loadPlugins($plugins);
        }
    }

    /**
     * Instantiate and remember Plugins of given types.
     *
     * @param array $plugins Plugin types (order matters)
     *
     * @return void
     */
    public function loadPlugins(array $plugins)
    {
        foreach (array_unique($plugins) as $class) {
            $this->loadPlugin($class);
        }
    }

    /**
     * Instantiate and remember a single Plugin of given type.
     *
     * @param string|Plugin $class Plugin type or instance
     *
     * @return void
     */
    public function loadPlugin($class)
    {
        if ($class instanceof Plugin or $class instanceof Controller) {
            $instance = $class;
            $class = get_class($class);
        } elseif (is_string($class) and class_exists($class)) {
            $instance = new $class($this->context);
        } else {
            throw new InvalidArgumentException();
        }

        if ($this->isLoaded($class)) {
            return;
        }

        $this->plugins[$class] = $instance;

        $this->registerHooks($class);
    }

    /**
     * Check if a Plugin was loaded already.
     *
     * @param string $class Plugin type
     *
     * @return bool
     */
    public function isLoaded(String $class)
    {
        return array_key_exists($class, $this->plugins);
    }

    /**
     * Pass configuration array to a plugin instance.
     *
     * @param string $class   FQN of the plugin
     * @param array  $options Array of options for the plugin
     *
     * @return void
     */
    public function configurePlugin(String $class, array $options)
    {
        if ($this->isLoaded($class)) {
            $this->plugins[$class]->configure($options);
        }
    }

    /**
     * Pass the `$argument` to all plugins registered for `$hook`
     * consecutively and return the result of the last plugin.
     *
     * @param string $hook     FQN of the hook interface
     * @param mixed  $argument Argument passed to the first hook
     *
     * @return mixed Return value of the last hook
     */
    public function applyHooksWithArgument(String $hook, $argument)
    {
        $method_name = static::getHookMethodName($hook);

        // Call all plugins, passing the return value of the
        // previous hook as first argument to the next
        foreach ($this->getPluginsForHook($hook) as $plugin) {
            $argument = $plugin->$method_name($argument);
        }

        return $argument;
    }

    /**
     * Run through all plugins registered on given hook, not passing any arguments.
     *
     * @param string $hook FQN of the hook interface
     *
     * @return void
     */
    public function applyHooks(String $hook)
    {
        $method_name = static::getHookMethodName($hook);

        foreach ($this->getPluginsForHook($hook) as $plugin) {
            $plugin->$method_name();
        }
    }

    /**
     * Register hooks for given plugin type.
     *
     * @param string $class Plugin type
     *
     * @return void
     */
    protected function registerHooks(String $class)
    {
        $hooks = class_implements($class);
        foreach ($hooks as $hook) {
            $this->registerHook($class, $hook);
        }
    }

    /**
     * Register a plugin to a single hook.
     *
     * @param Plugin $class The plugin class
     * @param string $hook  FQN of the hook interface
     *
     * @return void
     */
    protected function registerHook(String $class, String $hook)
    {
        if (!static::isHook($hook)) {
            return;
        }

        if (!$this->hookExists($hook)) {
            $this->hooks[$hook] = [];
        }

        $this->hooks[$hook][] = $class;
    }

    /**
     * Check if there are any Plugins registered on a hook.
     *
     * @param string $hook Hook Interface
     *
     * @return bool
     */
    protected function hookExists(String $hook)
    {
        return array_key_exists($hook, $this->hooks);
    }

    /**
     * Get all plugin instances for given hook in the order in which they were loaded.
     *
     * @param string $hook Hook Interface
     *
     * @return array
     */
    protected function getPluginsForHook(String $hook)
    {
        $instances = [];

        foreach ($this->getPluginClassesForHook($hook) as $class) {
            if ($this->isLoaded($class)) {
                $instances[] = $this->plugins[$class];
            }
        }

        return $instances;
    }

    /**
     * Get all Plugin Types that are registered on given hook in the order they were loaded.
     *
     * @param string $hook Hook Interface
     *
     * @return array
     */
    protected function getPluginClassesForHook(String $hook)
    {
        if ($this->hookExists($hook)) {
            return $this->hooks[$hook];
        }

        return [];
    }

    /**
     * Turn the FQN of a hook Interface into its corresponding method name.
     *
     * @param string $fqn FQN of the hook Interface
     *
     * @return string Name of the hook method
     */
    public static function getHookMethodName(String $fqn)
    {
        $without_namespace = array_last(explode('\\', $fqn));

        return lcfirst($without_namespace);
    }

    /**
     * Test whether a Hook is.
     *
     * @param string $class [description]
     *
     * @return bool [description]
     */
    public static function isHook(String $class)
    {
        return is_subclass_of($class, Hook::class, true);
    }
}