samsonos/php_core

View on GitHub
src/Module.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php declare(strict_types=1);
namespace samsonphp\core;

use samson\core\File;
use samsonframework\core\ResourcesInterface;
use samsonframework\core\SystemInterface;
use samsonframework\core\ViewInterface;
use samsonframework\resource\ResourceMap;
use samsonphp\core\deprecated\iModule;
use samsonphp\core\exception\ControllerActionNotFound;
use samsonphp\core\exception\ViewPathNotFound;
use samsonphp\core\exception\ViewVariableNotFound;

/**
 * Модуль системы
 *
 * @author Vitaly Iegorov <vitalyiegorov@gmail.com>
 * @deprecated Will be merged with ExternalModule
 */
class Module implements \ArrayAccess, iModule
{
    /** Static module instances collection */
    public static $instances = array();

    /** Uniquer identifier to check pointers */
    public $uid;

    /** @var ResourcesInterface Pointer to module resource map */
    public $resourceMap;

    /** @var array TODO: WTF? */
    public $composerParameters = array();

    /** Module views collection */
    protected $views = array();

    /** Module location */
    protected $path = '';

    /** Unique module identifier */
    protected $id;

    /** Path to view for rendering */
    protected $view_path = self::VD_POINTER_DEF;

    /** Pointer to view data entry */
    protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));

    /** Collection of data for view rendering, filled with default pointer */
    protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));

    /** Name of current view context entry */
    protected $view_context = self::VD_POINTER_DEF;

    /** Unique module cache path in local web-application */
    protected $cache_path;

    /** @var SystemInterface Instance for interaction with framework */
    protected $system;

    /**
     * Constructor
     *
     * @param string $id Module unique identifier
     * @param string $path Module location
     * @param ResourcesInterface $resourceMap Pointer to module resource map
     * @param SystemInterface $system
     */
    public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system)
    {
        // Inject generic module dependencies
        $this->system = $system;

        // Store pointer to module resource map
        // TODO: Should be changed or removed
        $this->resourceMap = $resourceMap = ResourceMap::get($path);
        // Save views list
        $this->views = $resourceMap->views;

        // Set default view context name
        $this->view_context = self::VD_POINTER_DEF;

        // Set up default view data pointer
        $this->data = &$this->view_data[$this->view_context];

        // Set module identifier
        $this->id = $id;

        // Set path to module
        $this->path(realpath($path));

        // Generate unique module identifier
        $this->uid = rand(0, 9999999) . '_' . microtime(true);

        // Add to module identifier to view data stack
        $this->data['id'] = $this->id;

        // Generate unique module cache path in local web-application
        $this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/';

        // Save ONLY ONE copy of this instance in static instances collection,
        // avoiding rewriting by cloned modules
        !isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : '';

        // Make view path relative to module - remove module path from view path
        $this->views = str_replace($this->path, '', $this->views);

        //elapsed('Registering module: '.$this->id.'('.$path.')' );
    }

    /** @see iModule::path() */
    public function path($value = null)
    {
        // Если передан параметр - установим его
        if (func_num_args()) {
            $this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '';

            return $this;
        } // Вернем относительный путь к файлам модуля
        else return $this->path;
    }

    /**    @see iModule::title() */
    public function title($title = null)
    {
        return $this->set($title, 'title');
    }

    /** @see iModule::set() */
    public function set($value, $field = null)
    {
        $this->__set($value, $field);

        return $this;
    }

    /**    @see iModule::id() */
    public function id()
    {
        return $this->id;
    }

    /** @see iModuleViewable::toView() */
    public function toView($prefix = null, array $restricted = array())
    {
        // Get all module data variables
        $view_data = array_merge($this->data, get_object_vars($this));

        // Remove plain HTML from view data
        unset($view_data[self::VD_HTML]);

        return $view_data;
    }

    /** @see iModule::html() */
    public function html($value)
    {
        $this->data[self::VD_HTML] = $value;

        return $this;
    }

    public function view($viewPath)
    {
        if (is_a($viewPath, ViewInterface::class)) {
            $this->view_path = $viewPath;
        } elseif (is_string($viewPath)) {
            // Find full path to view file
            $foundViewPath = $this->findView($viewPath);

            // We could not find view
            if ($foundViewPath !== false) {
                // Switch view context to founded module view
                $this->viewContext($foundViewPath);

                // Set current view path
                $this->view_path = $foundViewPath;
            } else {
                throw(new ViewPathNotFound($viewPath));
            }
        }

        // Продолжим цепирование
        return $this;
    }

    /**
     * Find view file by its part in module view resources and return full path to it.
     *
     * @param string $viewPath Part of path to module view file
     * @return string Full path to view file
     */
    public function findView($viewPath)
    {
        // Remove file extension for correct array searching
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);

        // Try to find passed view_path in  resources views collection
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
            // Sort view paths to get the shortest path
            usort($view, array($this, 'sortStrings'));

            // Set current full view path as last found view
            return end($view);
        }

        return false;
    }

    /**
     * Perform module view context switching
     * @param string $view_path New view context name
     */
    protected function viewContext($view_path)
    {
        // Pointer to NEW view data context
        $new = &$this->view_data[$view_path];

        // Pointer to OLD view data context
        $old = &$this->view_data[$this->view_context];

        // If we are trying to switch to NEW view context
        if ($this->view_context !== $view_path) {
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );

            // Create new entry in view data collection if it does not exists
            if (!isset($this->view_data[$view_path])) {
                // Create new view data record
                $new = array();

                // If current view data context has view data
                if (isset($old)) {
                    //elapsed($old);
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');

                    // Copy default view context view data to new view context
                    $new = array_merge($new, $old);
                }

                // Clear plain HTML for new view context
                $new[self::VD_HTML] = '';
            }

            // Change view data pointer to appropriate view data entry
            $this->data = &$new;

            // Save current context name
            $this->view_context = $view_path;
        }
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
    }

    /**
     * @param null $controller
     *
     * @throws ControllerActionNotFound
     */
    public function render($controller = null)
    {
        // Switch current system active module
        $old = $this->system->active($this);

        // If specific controller action should be run
        if (isset($controller)) {
            /**
             * TODO: This would be removed in next major version, here should be
             * passed real controller method for execution.
             */
            $controller = method_exists($this, iModule::OBJ_PREFIX . $controller)
                ? iModule::OBJ_PREFIX . $controller
                : $controller;

            // Define if this is a procedural controller or OOP
            $callback = array($this, $controller);

            // If this controller action is present
            if (method_exists($this, $controller)) {
                // Get passed arguments
                $parameters = func_get_args();
                // Remove first as its a controller action name
                array_shift($parameters);
                // Perform controller action with passed parameters
                call_user_func_array($callback, $parameters);
            } else {
                throw new ControllerActionNotFound($this->id . '#' . $controller);
            }
        }

        // Output view
        echo $this->output();

        // Restore previous active module
        $this->system->active($old);
    }

    /**    @see iModule::output() */
    public function output()
    {
        // If view path not specified - use current correct view path
        $viewPath = $this->view_path;

        if (is_string($viewPath)) {

            //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');

            // Switch view context to new module view
            $this->viewContext($viewPath);

            //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
            //elapsed(array_keys($this->view_data));

            // Get current view context plain HTML
            $out = $this->data[self::VD_HTML];

            // If view path specified
            if (isset($viewPath{0})) {
                // Временно изменим текущий модуль системы
                $old = $this->system->active($this);

                // Прорисуем представление модуля
                $out .= $this->system->render($this->path . $viewPath, $this->data);

                // Вернем на место текущий модуль системы
                $this->system->active($old);
            }

            // Clear currently outputted view context from VCS
            unset($this->view_data[$viewPath]);

            // Get last element from VCS
            end($this->view_data);

            // Get last element from VCS name
            $this->view_context = key($this->view_data);

            // Set internal view data pointer to last VCS entry
            $this->data = &$this->view_data[$this->view_context];

            // Return view path to previous state
            $this->view_path = $this->view_context;

            // Вернем результат прорисовки
            return $out;

        } elseif (is_a($viewPath, ViewInterface::class)) {
            /** @var ViewInterface $viewPath */
            return $viewPath->output();
        }
    }

    /** Magic method for calling un existing object methods */
    public function __call($method, $arguments)
    {
        //elapsed($this->id.' - __Call '.$method);

        // If value is passed - set it
        if (count($arguments)) {
            // If first argument is object or array - pass method name as second parameter
            if (is_object($arguments[0]) || is_array($arguments[0])) {
                $this->__set($arguments[0], $method);
            } else { // Standard logic
                $this->__set($arguments[0], $method);
            }
        }

        // Chaining
        return $this;
    }

    // Магический метод для получения переменных представления модуля

    /** Обработчик сериализации объекта */
    public function __sleep()
    {
        return array('id', 'path', 'data', 'views');
    }

    /** Обработчик десериализации объекта */
    public function __wakeup()
    {
        // Fill global instances
        self::$instances[$this->id] = &$this;

        // Set up default view data pointer
        $this->view_data[self::VD_POINTER_DEF] = $this->data;

        // Set reference to view context entry
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
    }

    // TODO: Переделать обработчик в одинаковый вид для объектов и простых

    /** Группа методов для доступа к аттрибутам в виде массива */
    public function offsetSet($value, $offset)
    {
        $this->__set($offset, $value);
    }

    public function offsetGet($offset)
    {
        return $this->__get($offset);
    }

    // Магический метод для установки переменных представления модуля

    public function __get($field)
    {
        // Установим пустышку как значение переменной
        $result = null;

        // Если указанная переменная представления существует - получим её значение
        if (isset($this->data[$field])) {
            $result = &$this->data[$field];
        } else {
            throw(new ViewVariableNotFound($field));
        }

        // Иначе вернем пустышку
        return $result;
    }

    public function __set($value, $field = null)
    {
        if (is_object($field) || is_array($field)) {
            $tempValue = $field;
            $field = $value;
            $value = $tempValue;

            // TODO: Will be added in next major version
            //throw new \Exception('ViewInterface::set($value, $name) has changed, first arg is variable second is name or prefix');
        }

        // This is object
        if (is_object($value) && is_a($value, 'samsonframework\core\RenderInterface')) {
            $this->_setObject($value, $field);
        } elseif (is_array($value)) { // If array is passed
            $this->_setArray($value, $field);
        } else { // Set view variable
            $this->data[$field] = $value;
        }
    }

    public function offsetUnset($offset)
    {
        unset($this->data[$offset]);
    }

    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    /** Sort array by string length */
    protected function sortStrings($a, $b)
    {
        return strlen($b) - strlen($a);
    }

    /**
     * Create unique module cache folder structure in local web-application.
     *
     * @param string $file Path to file relative to module cache location
     * @param boolean $clear Flag to perform generic cache folder clearence
     * @return boolean TRUE if cache file has to be regenerated
     */
    protected function cache_refresh(&$file, $clear = true, $folder = null)
    {
        // Add slash to end
        $folder = isset($folder) ? rtrim($folder, '/') . '/' : '';

        $path = $this->cache_path . $folder;

        // If module cache folder does not exists - create it
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }

        // Build full path to cached file
        $file = $path . $file;

        // If cached file does not exsits
        if (!file_exists($file)) {
            // If clearence flag set to true - clear all files in module cache directory with same extension
            if ($clear) {
                File::clear($path, pathinfo($file, PATHINFO_EXTENSION));
            }

            // Signal for cache file regeneration
            return true;
        }

        return false;
    }

    /**
     *
     * @param unknown $object
     * @param string $viewprefix
     */
    private function _setObject($object, $viewprefix = null)
    {
        // Generate viewprefix as only lowercase classname without NS if it is not specified
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(ns_classname(get_class($object)), 'UTF-8');

        // Save object to view data
        $this->data[$class_name] = $object;

        // Add separator
        $class_name .= '_';

        // Generate objects view array data and merge it with view data
        $this->data = array_merge($this->data, $object->toView($class_name));
    }

    /**
     *
     * @param unknown $array
     * @param string $viewprefix
     */
    private function _setArray($array, $viewprefix = null)
    {
        // Save array to view data
        $this->data[$viewprefix] = $array;

        // Add array values to view data
        $this->data = array_merge($this->data, $array);
    }
}