felixarntz/plugin-lib

View on GitHub
src/plugin.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
/**
 * Plugin main file
 *
 * @package Leaves_And_Love\Plugin_Lib
 * @since 1.0.0
 */

if ( ! class_exists( 'Leaves_And_Love_Plugin' ) ) :

    /**
     * Main plugin class.
     *
     * Takes care of initializing the plugin.
     *
     * @since 1.0.0
     */
    abstract class Leaves_And_Love_Plugin {
        /**
         * The plugin version.
         *
         * @since 1.0.0
         * @var string
         */
        protected $version;

        /**
         * The plugin prefix.
         *
         * @since 1.0.0
         * @var string
         */
        protected $prefix;

        /**
         * The plugin vendor name.
         *
         * @since 1.0.0
         * @var string
         */
        protected $vendor_name;

        /**
         * The plugin project name.
         *
         * @since 1.0.0
         * @var string
         */
        protected $project_name;

        /**
         * The minimum required PHP version.
         *
         * @since 1.0.0
         * @var string
         */
        protected $minimum_php;

        /**
         * The minimum required WordPress version.
         *
         * @since 1.0.0
         * @var string
         */
        protected $minimum_wp;

        /**
         * Path to the plugin's main file.
         *
         * @since 1.0.0
         * @var string
         */
        protected $main_file;

        /**
         * Relative base path to the other files of this plugin.
         *
         * @since 1.0.0
         * @var string
         */
        protected $basedir_relative;

        /**
         * Messages printed to the user.
         *
         * @since 1.0.0
         * @var array
         */
        protected $messages = array();

        /**
         * Error object if the class cannot be initialized.
         *
         * @since 1.0.0
         * @var WP_Error|null
         */
        protected $error;

        /**
         * Constructor.
         *
         * @since 1.0.0
         *
         * @param string $main_file        Path to the plugin's main file.
         * @param string $basedir_relative The relative base path to the other files of this plugin.
         *
         * @codeCoverageIgnore
         */
        public function __construct( $main_file, $basedir_relative = '' ) {
            $this->main_file        = $main_file;
            $this->basedir_relative = $basedir_relative;
            $this->minimum_php      = '5.5';
            $this->minimum_wp       = '4.7';

            $this->load_base_properties();
            $this->load_textdomain();
            $this->load_messages();

            $this->error = $this->check();
        }

        /**
         * Dummy magic method to prevent Content Organizer from being cloned.
         *
         * @since 1.0.0
         *
         * @codeCoverageIgnore
         */
        public function __clone() {
            _doing_it_wrong( __FUNCTION__, $this->messages['cheatin_huh'], '1.0.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        }

        /**
         * Dummy magic method to prevent Content Organizer from being unserialized.
         *
         * @since 1.0.0
         *
         * @codeCoverageIgnore
         */
        public function __wakeup() {
            _doing_it_wrong( __FUNCTION__, $this->messages['cheatin_huh'], '1.0.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        }

        /**
         * Returns class instances for the plugin.
         *
         * This magic method allows you to call methods with the name of a class property, which will
         * then return the respective instance.
         *
         * @since 1.0.0
         *
         * @param string $method_name Name of the method to call.
         * @param array  $args        Method arguments.
         * @return object|null Either the class instance denoted by the method name, or null if it doesn't exist.
         */
        public function __call( $method_name, $args ) {
            if ( in_array(
                $method_name,
                array(
                    'get_activation_hook',
                    'get_deactivation_hook',
                    'get_uninstall_hook',
                ),
                true
            ) ) {
                return false;
            }

            if ( isset( $this->$method_name ) && is_a( $this->$method_name, 'Leaves_And_Love\Plugin_Lib\Service' ) ) {
                return $this->$method_name;
            }

            if ( 'error' === $method_name && is_wp_error( $this->error ) ) {
                return $this->error;
            }

            return null;
        }

        /**
         * Returns the plugin version number.
         *
         * @since 1.0.0
         *
         * @return string Version number.
         */
        public function version() {
            return $this->version;
        }

        /**
         * Returns the full path to a relative path for a plugin file or directory.
         *
         * @since 1.0.0
         *
         * @param string $rel_path Relative path.
         * @return string Full path.
         */
        public function path( $rel_path ) {
            return plugin_dir_path( $this->main_file ) . $this->basedir_relative . ltrim( $rel_path, '/' );
        }

        /**
         * Returns the full URL to a relative path for a plugin file or directory.
         *
         * @since 1.0.0
         *
         * @param string $rel_path Relative path.
         * @return string Full URL.
         */
        public function url( $rel_path ) {
            return plugin_dir_url( $this->main_file ) . $this->basedir_relative . ltrim( $rel_path, '/' );
        }

        /**
         * Loads the plugin by registering the autoloader and instantiating the general classes.
         *
         * This method can only be executed once.
         *
         * @since 1.0.0
         */
        public function load() {
            if ( did_action( $this->prefix . 'loaded' ) ) {
                return;
            }

            if ( ! $this->dependencies_loaded() ) {
                $vendor_autoload = $this->path( 'vendor/autoload.php' );
                if ( file_exists( $vendor_autoload ) ) {
                    require_once $vendor_autoload;
                }
            }

            Leaves_And_Love_Autoloader::register_namespace( $this->vendor_name, $this->project_name, $this->path( 'src/' ) );

            $this->instantiate_services();

            /**
             * Fires after the plugin has loaded.
             *
             * @since 1.0.0
             *
             * @param Leaves_And_Love_Plugin $plugin The plugin instance.
             */
            do_action( $this->prefix . 'loaded', $this );
        }

        /**
         * Starts the plugin by adding the necessary hooks.
         *
         * This method can only be executed once.
         *
         * @since 1.0.0
         */
        public function start() {
            if ( did_action( $this->prefix . 'started' ) ) {
                return;
            }

            $this->add_hooks();

            /**
             * Fires after the plugin has started.
             *
             * @since 1.0.0
             *
             * @param Leaves_And_Love_Plugin $plugin The plugin instance.
             */
            do_action( $this->prefix . 'started', $this );
        }

        /**
         * Checks whether the plugin can run on this setup.
         *
         * @since 1.0.0
         *
         * @return WP_Error|null Error object if the plugin cannot run on this setup, null otherwise.
         */
        protected function check() {
            if ( version_compare( phpversion(), $this->minimum_php, '<' ) ) {
                return new WP_Error( $this->prefix . 'outdated_php', sprintf( $this->messages['outdated_php'], $this->minimum_php ) );
            }

            if ( version_compare( get_bloginfo( 'version' ), $this->minimum_wp, '<' ) ) {
                return new WP_Error( $this->prefix . 'outdated_wordpress', sprintf( $this->messages['outdated_wp'], $this->minimum_wp ) );
            }

            return null;
        }

        /**
         * Instantiates a specific plugin service.
         *
         * @since 1.0.0
         *
         * @param string $class_name The class name, without basic namespace.
         * @param mixed  $args,...   Optional arguments to pass to the constructor. It is recommended to pass
         *                           the plugin prefix as first of these arguments.
         *
         * @return Leaves_And_Love\Plugin_Lib\Service|null The plugin service instance, or null if invalid.
         */
        protected function instantiate_plugin_service( $class_name ) {
            $params = func_get_args();

            $params[0] = $this->vendor_name . '\\' . $this->project_name . '\\' . $class_name;

            return $this->instantiate_service( $params );
        }

        /**
         * Instantiates a specific library service.
         *
         * @since 1.0.0
         *
         * @param string $class_name The class name, without basic namespace.
         * @param mixed  $args,...   Optional arguments to pass to the constructor. It is recommended to pass
         *                           the plugin prefix as first of these arguments.
         *
         * @return Leaves_And_Love\Plugin_Lib\Service|null The library service instance, or null if invalid.
         */
        protected function instantiate_library_service( $class_name ) {
            $params = func_get_args();

            $params[0] = 'Leaves_And_Love\\Plugin_Lib\\' . $class_name;

            return $this->instantiate_service( $params );
        }

        /**
         * Instantiates a specific plugin class.
         *
         * @since 1.0.0
         *
         * @param string $class_name The class name, without basic namespace.
         * @param mixed  $args,...   Optional arguments to pass to the constructor.
         * @return object The class instance.
         */
        protected function instantiate_plugin_class( $class_name ) {
            $params = func_get_args();

            $params[0] = $this->vendor_name . '\\' . $this->project_name . '\\' . $class_name;

            return $this->instantiate_class( $params );
        }

        /**
         * Instantiates a specific library class.
         *
         * @since 1.0.0
         *
         * @param string $class_name The class name, without basic namespace.
         * @param mixed  $args,...   Optional arguments to pass to the constructor.
         * @return object The class instance.
         */
        protected function instantiate_library_class( $class_name ) {
            $params = func_get_args();

            $params[0] = 'Leaves_And_Love\\Plugin_Lib\\' . $class_name;

            return $this->instantiate_class( $params );
        }

        /**
         * Loads the base properties of the class.
         *
         * @since 1.0.0
         */
        abstract protected function load_base_properties();

        /**
         * Loads the plugin's textdomain.
         *
         * @since 1.0.0
         */
        abstract protected function load_textdomain();

        /**
         * Loads the class messages.
         *
         * @since 1.0.0
         */
        abstract protected function load_messages();

        /**
         * Instantiates the plugin services.
         *
         * @since 1.0.0
         */
        abstract protected function instantiate_services();

        /**
         * Adds the necessary plugin hooks.
         *
         * @since 1.0.0
         */
        abstract protected function add_hooks();

        /**
         * Loads the plugin textdomain file.
         *
         * This is a helper method that wraps WordPress' `load_plugin_textdomain()`.
         *
         * @since 1.0.0
         *
         * @param string $textdomain Textdomain to load.
         * @param string $rel_path   Optional. Relative path to the languages directory. Default empty.
         * @return bool True if the textdomain file could be loaded, false otherwise.
         */
        protected function load_plugin_textdomain( $textdomain, $rel_path = '' ) {
            if ( empty( $rel_path ) ) {
                return load_plugin_textdomain( $textdomain );
            }

            $plugin_rel_path = dirname( plugin_basename( $this->main_file ) ) . '/' . $this->basedir_relative . trim( $rel_path, '/' );

            return load_plugin_textdomain( $textdomain, false, $plugin_rel_path );
        }

        /**
         * Checks whether the dependencies have been loaded.
         *
         * If this method returns false, the plugin will attempt to require the composer-generated
         * autoloader script. If your plugin uses additional dependencies, override this method with
         * a check whether these dependencies already exist.
         *
         * @since 1.0.0
         *
         * @return bool True if the dependencies are loaded, false otherwise.
         */
        protected function dependencies_loaded() {
            return true;
        }

        /**
         * Instantiates a specific service.
         *
         * This private method is called only internally.
         *
         * @since 1.0.0
         *
         * @param array $params Array of parameters for the originally called method.
         * @return Leaves_And_Love\Plugin_Lib\Service|null The service instance, or null if invalid.
         */
        private function instantiate_service( $params ) {
            if ( count( $params ) === 1 ) {
                array_push( $params, $this->prefix );
            } else {
                $key = array_search( $this->prefix, $params, true );
                if ( false === $key ) {
                    array_splice( $params, 1, 0, $this->prefix );
                } elseif ( 1 !== $key ) {
                    $value = array_splice( $params, $key, 1 );
                    array_splice( $params, 1, 0, $value );
                }
            }

            return call_user_func_array( array( 'Leaves_And_Love\Plugin_Lib\Service_Instantiator', 'instantiate' ), $params );
        }

        /**
         * Instantiates a specific class.
         *
         * This private method is called only internally.
         *
         * @since 1.0.0
         *
         * @param array $params Array of parameters for the originally called method.
         * @return object The class instance.
         */
        private function instantiate_class( $params ) {
            $class_name = array_shift( $params );

            if ( ! empty( $params ) ) {
                $reflected_class = new ReflectionClass( $class_name );
                return $reflected_class->newInstanceArgs( $params );
            }

            return new $class_name();
        }
    }

endif;