Skip to content
Advertisement

How to design a simple plugin based on pico CMS without using required_once?

I’m hoping to design a simple plugin based on pico CMS and call it in a protected function in my main class, called App.

Plugin is called AppHelper.

Attempt

I added a required_once on the top of App class:

 require_once "/Library/WebServer/usco/master/plugins/AppHelper.php";

and tested using this (which first AppHelper::isMobile is not right):

// ********************MOBILE OR DESKTOP***********************//
if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
    $viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
} else {
    $viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
}
// ********************MOBILE OR DESKTOP***********************//

in a protected method:

protected function loadConfig()
{
    $config = null;
    if (file_exists($this->getConfigDir() . 'config.php')) {
        require $this->getConfigDir() . 'config.php';
    }

    // ********************MOBILE OR DESKTOP***********************//
    if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
        $viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
    } else {
        $viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
    }
    // ********************MOBILE OR DESKTOP***********************//

    $defaultConfig = array(
        'company_name' => 'USCO',
        'page_title' => 'USCO',
        'base_url' => '',
        'rewrite_url' => null,
        'date_format' => '%D %T',
        'twig_config' => null,
        'blog_content_dir' => null,
        'blog_content_path' => null,
        'blog_articles_dir' => null,
        'blog_articles_path' => null,
        'blog_content_ext' => '.md',
        'device' => $viewSetting["device"],
        'timezone' => '',
    );

    $this->config = is_array($this->config) ? $this->config : array();
    $this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig;
    if (empty($this->config['base_url'])) {
        $this->config['base_url'] = $this->getBaseUrl();
    } else {
        $this->config['base_url'] = rtrim($this->config['base_url'], '/') . '/';
    }

    if ($this->config['rewrite_url'] === null) {
        $this->config['rewrite_url'] = $this->isUrlRewritingEnabled();
    }

    $local_domain = "usco.loc";
    if ($_SERVER['SERVER_NAME'] === $local_domain) {
        $defaultTwigConfig = array('cache' => false, 'autoescape' => false, 'debug' => true);
        // $defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
    } else {
        $defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
    }
    if (!is_array($this->config['twig_config'])) {
        $this->config['twig_config'] = $defaultTwigConfig;
    } else {
        $this->config['twig_config'] += $defaultTwigConfig;
    }

    $this->config['blog_content_path'] = $this->getAbsolutePath($this->config['blog_content_dir'], true);
    $this->config['blog_articles_path'] = $this->config['blog_content_path'] . $this->config['blog_articles_dir'];

    if (empty($this->config['timezone'])) {
        // explicitly set a default timezone to prevent a E_NOTICE
        // when no timezone is set; the `date_default_timezone_get()`
        // function always returns a timezone, at least UTC
        $this->config['timezone'] = @date_default_timezone_get();
    }
    date_default_timezone_set($this->config['timezone']);
}

and works okay with just a warning. However, this is not right.

How do I correctly design/add this plugin without required_once and autoload it using composer install, like all my other plugins that function so?

AppHelper

class AppHelper extends AbstractAppPlugin
{

    public function __construct(App $app)
    {
        parent::__construct($app);
    }

    public function isMobile($useragent)
    {
        if (
            preg_match('/(android|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows ce|xda|xiino/i', $useragent)
            ||
            preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i', substr($useragent, 0, 4))
        ) {
            return true; // is mobile
        } else {
            return false; // is not mobile
        };

    }

}

AbstractAppPlugin

abstract class AbstractAppPlugin implements AppPluginInterface
{
    /**
     * Current instance of App
     *
     * @see AppPluginInterface::getApp()
     * @var App
     */
    private $app;

    /**
     * Boolean indicating if this plugin is enabled (true) or disabled (false)
     *
     * @see AppPluginInterface::isEnabled()
     * @see AppPluginInterface::setEnabled()
     * @var boolean
     */
    protected $enabled = true;

    /**
     * Boolean indicating if this plugin was ever enabled/disabled manually
     *
     * @see AppPluginInterface::isStatusChanged()
     * @var boolean
     */
    protected $statusChanged = false;

    /**
     * List of plugins which this plugin depends on
     *
     * @see AbstractAppPlugin::checkDependencies()
     * @see AppPluginInterface::getDependencies()
     * @var string[]
     */
    protected $dependsOn = array();

    /**
     * List of plugin which depend on this plugin
     *
     * @see AbstractAppPlugin::checkDependants()
     * @see AppPluginInterface::getDependants()
     * @var object[]
     */
    private $dependants;

    /**
     * @see AppPluginInterface::__construct()
     */
    public function __construct(App $app)
    {
        $this->app = $app;
    }

    /**
     * @see AppPluginInterface::handleEvent()
     */
    public function handleEvent($eventName, array $params)
    {
        // plugins can be enabled/disabled using the config
        if ($eventName === 'onConfigLoaded') {
            $pluginEnabled = $this->getConfig(get_called_class() . '.enabled');
            if ($pluginEnabled !== null) {
                $this->setEnabled($pluginEnabled);
            } else {
                $pluginConfig = $this->getConfig(get_called_class());
                if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) {
                    $this->setEnabled($pluginConfig['enabled']);
                } elseif ($this->enabled) {
                    // make sure dependencies are already fulfilled,
                    // otherwise the plugin needs to be enabled manually
                    try {
                        $this->checkDependencies(false);
                    } catch (RuntimeException $e) {
                        $this->enabled = false;
                    }
                }
            }
        }

        if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) {
            if (method_exists($this, $eventName)) {
                call_user_func_array(array($this, $eventName), $params);
            }
        }
    }

    /**
     * @see AppPluginInterface::setEnabled()
     */
    public function setEnabled($enabled, $recursive = true, $auto = false)
    {
        $this->statusChanged = (!$this->statusChanged) ? !$auto : true;
        $this->enabled = (bool) $enabled;

        if ($enabled) {
            $this->checkDependencies($recursive);
        } else {
            $this->checkDependants($recursive);
        }
    }

    /**
     * @see AppPluginInterface::isEnabled()
     */
    public function isEnabled()
    {
        return $this->enabled;
    }

    /**
     * @see AppPluginInterface::isStatusChanged()
     */
    public function isStatusChanged()
    {
        return $this->statusChanged;
    }

    /**
     * @see AppPluginInterface::getApp()
     */
    public function getApp()
    {
        return $this->app;
    }

    /**
     * Passes all not satisfiable method calls to App
     *
     * @see    App
     * @param  string $methodName name of the method to call
     * @param  array  $params     parameters to pass
     * @return mixed              return value of the called method
     */
    public function __call($methodName, array $params)
    {
        if (method_exists($this->getApp(), $methodName)) {
            return call_user_func_array(array($this->getApp(), $methodName), $params);
        }

        throw new BadMethodCallException(
            'Call to undefined method ' . get_class($this->getApp()) . '::' . $methodName . '() '
            . 'through ' . get_called_class() . '::__call()'
        );
    }

    /**
     * Enables all plugins which this plugin depends on
     *
     * @see    AppPluginInterface::getDependencies()
     * @param  boolean $recursive enable required plugins automatically
     * @return void
     * @throws RuntimeException   thrown when a dependency fails
     */
    protected function checkDependencies($recursive)
    {
        foreach ($this->getDependencies() as $pluginName) {
            try {
                $plugin = $this->getPlugin($pluginName);
            } catch (RuntimeException $e) {
                throw new RuntimeException(
                    "Unable to enable plugin '" . get_called_class() . "': "
                    . "Required plugin '" . $pluginName . "' not found"
                );
            }

            // plugins which don't implement AppPluginInterface are always enabled
            if (is_a($plugin, 'AppPluginInterface') && !$plugin->isEnabled()) {
                if ($recursive) {
                    if (!$plugin->isStatusChanged()) {
                        $plugin->setEnabled(true, true, true);
                    } else {
                        throw new RuntimeException(
                            "Unable to enable plugin '" . get_called_class() . "': "
                            . "Required plugin '" . $pluginName . "' was disabled manually"
                        );
                    }
                } else {
                    throw new RuntimeException(
                        "Unable to enable plugin '" . get_called_class() . "': "
                        . "Required plugin '" . $pluginName . "' is disabled"
                    );
                }
            }
        }
    }

    /**
     * @see AppPluginInterface::getDependencies()
     */
    public function getDependencies()
    {
        return (array) $this->dependsOn;
    }

    /**
     * Disables all plugins which depend on this plugin
     *
     * @see    AppPluginInterface::getDependants()
     * @param  boolean $recursive disabled dependant plugins automatically
     * @return void
     * @throws RuntimeException   thrown when a dependency fails
     */
    protected function checkDependants($recursive)
    {
        $dependants = $this->getDependants();
        if (!empty($dependants)) {
            if ($recursive) {
                foreach ($this->getDependants() as $pluginName => $plugin) {
                    if ($plugin->isEnabled()) {
                        if (!$plugin->isStatusChanged()) {
                            $plugin->setEnabled(false, true, true);
                        } else {
                            throw new RuntimeException(
                                "Unable to disable plugin '" . get_called_class() . "': "
                                . "Required by manually enabled plugin '" . $pluginName . "'"
                            );
                        }
                    }
                }
            } else {
                $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' ';
                $dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'";
                throw new RuntimeException(
                    "Unable to disable plugin '" . get_called_class() . "': "
                    . "Required by " . $dependantsList
                );
            }
        }
    }

    /**
     * @see AppPluginInterface::getDependants()
     */
    public function getDependants()
    {
        if ($this->dependants === null) {
            $this->dependants = array();
            foreach ($this->getPlugins() as $pluginName => $plugin) {
                // only plugins which implement AppPluginInterface support dependencies
                if (is_a($plugin, 'AppPluginInterface')) {
                    $dependencies = $plugin->getDependencies();
                    if (in_array(get_called_class(), $dependencies)) {
                        $this->dependants[$pluginName] = $plugin;
                    }
                }
            }
        }

        return $this->dependants;
    }
}

Advertisement

Answer

I don’t see any namespace in your classes? Can you add how the classes map to the filesystem? There is a PHP-standard called PSR-4 which is commonly used. If this does not meet your requirements, composer offers a few alternative autoloading options that you can specify in your plugin’s composer.json.

See: https://getcomposer.org/doc/04-schema.md#autoload

For example if your plugin only consists of this one AppHelper class that is kept in a file src/AppHelper.php and the abstract base class you could just add a classmap with those 2 files to your composer.json:

...
"autoload" {
    "classmap": ["src/AppHelper.php", "src/AbstractAppHelper.php"]
},
...

Since both classes map to the classes this would work just as well with PSR-0:

...
"autoload" {
    "psr-0": {
        "\": "src/"
    }
},
...

This will add all the php files in the src directory to a corresponding class, so:

src/AppHelper.php         -> class AppHelper
src/AbstractAppHelper.php -> class AbstractAppHelper

and so on. You might run into trouble though as you are mapping into the global namespace. Using a custom namespace for those classes instead is probably a better choice and usually can be done quickly with the help of some tools like the refactoring helpers in PhpStorm.

edit: Sorry, I missed the link to the actual repository. In your case it should probably look like:

"autoload": {
    "psr-0": {
        "\": "master/plugins",
    }
}
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement