Completed
Push — master ( 128851...3506e7 )
by Vitaly
07:15
created

Module::render()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 37
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4

Importance

Changes 10
Bugs 5 Features 1
Metric Value
c 10
b 5
f 1
dl 0
loc 37
ccs 17
cts 17
cp 1
rs 8.5806
cc 4
eloc 15
nc 4
nop 1
crap 4
1
<?php
2
namespace samson\core;
3
4
// TODO: Разобраться почему с вызовом m()->render() во вьюхе, и почему не передаются параметры
5
use samsonframework\core\RenderInterface;
6
use samsonframework\core\ResourcesInterface;
7
use samsonframework\core\SystemInterface;
8
use samsonframework\core\ViewInterface;
9
use samsonphp\core\exception\ControllerActionNotFound;
10
use samsonphp\core\exception\ViewPathNotFound;
11
use samsonphp\core\exception\ViewVariableNotFound;
12
use Symfony\Component\Config\Definition\Exception\Exception;
13
14
/**
15
 * Модуль системы
16
 *
17
 * @author Vitaly Iegorov <[email protected]>
18
 * @version 1.0
19
 */
20
class Module implements iModule, \ArrayAccess
21
{
22
    /** Static module instances collection */
23
    public static $instances = array();
24
25
    /** Uniquer identifier to check pointers */
26
    public $uid;
27
28
    /** @var ResourcesInterface Pointer to module resource map */
29
    public $resourceMap;
30
31
    /** @var array TODO: WTF? */
32
    public $composerParameters = array();
33
34
    /** Module views collection */
35
    protected $views = array();
36
37
    /** Module location */
38
    protected $path = '';
39
40
    /** Unique module identifier */
41
    protected $id = '';
42
43
    /** Path to view for rendering */
44
    protected $view_path = self::VD_POINTER_DEF;
45
46
    /** Pointer to view data entry */
47
    protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
48
49
    /** Collection of data for view rendering, filled with default pointer */
50
    protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
51
52
    /** Name of current view context entry */
53
    protected $view_context = self::VD_POINTER_DEF;
54
55
    /** Unique module cache path in local web-application */
56
    protected $cache_path;
57
58
    /** @var SystemInterface Instance for interaction with framework */
59
    protected $system;
60
61
    /**
62
     * Constructor
63
     *
64
     * @param string $id Module unique identifier
65
     * @param string $path Module location
66 7
     * @param ResourcesInterface $resourceMap Pointer to module resource map
67
     * @param SystemInterface $system
68
     */
69 7
    public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system)
70
    {
71
        // Inject generic module dependencies
72 7
        $this->system = $system;
73
74 7
        // Store pointer to module resource map
75
        $this->resourceMap = &$resourceMap;
76
        // Save views list
77 7
        $this->views = $resourceMap->views;
0 ignored issues
show
Bug introduced by
Accessing views on the interface samsonframework\core\ResourcesInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
78
79
        // Set default view context name
80 7
        $this->view_context = self::VD_POINTER_DEF;
81
82
        // Set up default view data pointer
83 7
        $this->data = &$this->view_data[$this->view_context];
84
85
        // Set module identifier
86 7
        $this->id = $id;
87
88
        // Set path to module
89 7
        $this->path(realpath($path));
90
91
        // Generate unique module identifier
92 7
        $this->uid = rand(0, 9999999) . '_' . microtime(true);
93
94
        // Add to module identifier to view data stack
95 7
        $this->data['id'] = $this->id;
96
97
        // Generate unique module cache path in local web-application
98
        $this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/';
99 7
100
        // Save ONLY ONE copy of this instance in static instances collection,
101
        // avoiding rewriting by cloned modules
102 7
        !isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : '';
103
104
        // Make view path relative to module - remove module path from view path
105 7
        $this->views = str_replace($this->path, '', $this->views);
106
107
        //elapsed('Registering module: '.$this->id.'('.$path.')' );
108 7
    }
109
110
    /** @see iModule::path() */
111 7
    public function path($value = null)
112 7
    {
113
        // Если передан параметр - установим его
114 7
        if (func_num_args()) {
115
            $this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '';
116 1
117
            return $this;
118
        } // Вернем относительный путь к файлам модуля
119
        else return $this->path;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
120
    }
121
122
    /**    @see iModule::title() */
123
    public function title($title = null)
124
    {
125
        return $this->set($title, 'title');
126 1
    }
127
128 1
    /** @see iModule::set() */
129
    public function set($value, $field = null)
130 1
    {
131
        $this->__set($value, $field);
132
133
        return $this;
134 1
    }
135
136 1
    /**    @see iModule::id() */
137
    public function id()
138
    {
139
        return $this->id;
140
    }
141
142
    /** @see iModuleViewable::toView() */
143
    public function toView($prefix = null, array $restricted = array())
144
    {
145
        // Get all module data variables
146
        $view_data = array_merge($this->data, get_object_vars($this));
147
148
        // Remove plain HTML from view data
149
        unset($view_data[self::VD_HTML]);
150
151
        return $view_data;
152 1
    }
153
154 1
    /** @see iModule::html() */
155
    public function html($value)
156 1
    {
157
        $this->data[self::VD_HTML] = $value;
158
159 3
        return $this;
160
    }
161
162 3
    public function view($viewPath)
163
    {
164
        if (is_a($viewPath, ViewInterface::class)) {
165 3
            $this->view_path = $viewPath;
166
        } elseif (is_string($viewPath)) {
167 3
            // Find full path to view file
168
            $foundViewPath = $this->findView($viewPath);
169
170 3
            // We could not find view
171 3
            if ($foundViewPath !== false) {
172 1
                // Switch view context to founded module view
173
                $this->viewContext($foundViewPath);
174
175
                // Set current view path
176 3
                $this->view_path = $foundViewPath;
177
            } else {
178
                throw(new ViewPathNotFound($viewPath));
179
            }
180
        }
181
182
        // Продолжим цепирование
183
        return $this;
184
    }
185 3
186
    /**
187
     * Find view file by its part in module view resources and return full path to it.
188 3
     *
189
     * @param string $viewPath Part of path to module view file
190
     * @return string Full path to view file
191 3
     */
192
    public function findView($viewPath)
193 3
    {
194
        // Remove file extension for correct array searching
195
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);
196 3
197
        // Try to find passed view_path in  resources views collection
198
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
199 1
            // Sort view paths to get the shortest path
200
            usort($view, array($this, 'sortStrings'));
201
202
            // Set current full view path as last found view
203
            return end($view);
204
        }
205
206 4
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by samson\core\Module::findView of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
207
    }
208
209 4
    /**
210
     * Perform module view context switching
211
     * @param string $view_path New view context name
212 4
     */
213
    protected function viewContext($view_path)
214
    {
215 4
        // Pointer to NEW view data context
216
        $new = &$this->view_data[$view_path];
217
218
        // Pointer to OLD view data context
219 3
        $old = &$this->view_data[$this->view_context];
220
221 3
        // If we are trying to switch to NEW view context
222
        if ($this->view_context !== $view_path) {
223
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );
224 3
225
            // Create new entry in view data collection if it does not exists
226
            if (!isset($this->view_data[$view_path])) {
227
                // Create new view data record
228
                $new = array();
229 3
230 3
                // If current view data context has view data
231
                if (isset($old)) {
232
                    //elapsed($old);
233 3
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');
234 3
235
                    // Copy default view context view data to new view context
236
                    $new = array_merge($new, $old);
237 3
                }
238
239
                // Clear plain HTML for new view context
240 3
                $new[self::VD_HTML] = '';
241 3
            }
242
243 4
            // Change view data pointer to appropriate view data entry
244
            $this->data = &$new;
245
246 1
            // Save current context name
247
            $this->view_context = $view_path;
248
        }
249 1
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
250
    }
251
252 1
    /**
253
     * @param null $controller
254
     *
255
     * @throws ControllerActionNotFound
256
     */
257 1
    public function render($controller = null)
258 1
    {
259 1
        // Switch current system active module
260
        $old = &$this->system->active($this);
261
262 1
        // If specific controller action should be run
263
        if (isset($controller)) {
264
            /**
265 1
             * TODO: This would be removed in next major version, here should be
266
             * passed real controller method for execution.
267 1
             */
268
            $controller = method_exists($this, iModule::OBJ_PREFIX . $controller)
269 1
                ? iModule::OBJ_PREFIX . $controller
270
                : $controller;
271 1
272 1
            // Define if this is a procedural controller or OOP
273 1
            $callback = array($this, $controller);
274
275 1
            // If this controller action is present
276
            if (method_exists($this, $controller)) {
277
                // Get passed arguments
278 1
                $parameters = func_get_args();
279
                // Remove first as its a controller action name
280
                array_shift($parameters);
281 1
                // Perform controller action with passed parameters
282 1
                call_user_func_array($callback, $parameters);
283
            } else {
284
                throw new ControllerActionNotFound($this->id . '#' . $controller);
285 3
            }
286
        }
287
288 3
        // Output view
289
        echo $this->output();
290
291
        // Restore previous active module
292
        $this->system->active($old);
293 3
    }
294
295
    /**    @see iModule::output() */
296
    public function output()
297
    {
298
        // If view path not specified - use current correct view path
299 3
        $viewPath = $this->view_path;
300
301
        if (is_string($viewPath)) {
302 3
303
            //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');
304 2
305
            // Switch view context to new module view
306
            $this->viewContext($viewPath);
307 2
308
            //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
309
            //elapsed(array_keys($this->view_data));
310 2
311 2
            // Get current view context plain HTML
312
            $out = $this->data[self::VD_HTML];
313
314 3
            // If view path specified
315
            if (isset($viewPath{0})) {
316
                // Временно изменим текущий модуль системы
317 3
                $old = $this->system->active($this);
318
319
                // Прорисуем представление модуля
320 3
                $out .= $this->system->render($this->path . $viewPath, $this->data);
321
322
                // Вернем на место текущий модуль системы
323 3
                $this->system->active($old);
324
            }
325
        } elseif (is_a($viewPath, ViewInterface::class)) {
326 3
            /** @var ViewInterface $viewPath */
327
            $out .= $viewPath->output();
0 ignored issues
show
Bug introduced by
The variable $out seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
328
        }
329 3
330
        // Clear currently outputted view context from VCS
331
        unset($this->view_data[$viewPath]);
332
333 1
        // Get last element from VCS
334
        end($this->view_data);
335
336
        // Get last element from VCS name
337
        $this->view_context = key($this->view_data);
338 1
339
        // Set internal view data pointer to last VCS entry
340 1
        $this->data = &$this->view_data[$this->view_context];
341 1
342 1
        // Return view path to previous state
343 1
        $this->view_path = $this->view_context;
344
345 1
        // Вернем результат прорисовки
346
        return $out;
0 ignored issues
show
Bug introduced by
The variable $out does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
347
    }
348 1
349
    /** Magic method for calling un existing object methods */
350
    public function __call($method, $arguments)
351
    {
352
        //elapsed($this->id.' - __Call '.$method);
353
354 1
        // If value is passed - set it
355
        if (count($arguments)) {
356 1
            // If first argument is object or array - pass method name as second parameter
357
            if (is_object($arguments[0]) || is_array($arguments[0])) {
358
                $this->__set($arguments[0], $method);
359
            } else { // Standard logic
360 1
                $this->__set($arguments[0], $method);
361
            }
362
        }
363 1
364
        // Chaining
365
        return $this;
366 1
    }
367
368
    // Магический метод для получения переменных представления модуля
369 1
370 1
    /** Обработчик сериализации объекта */
371
    public function __sleep()
372
    {
373
        return array('id', 'path', 'data', 'views');
374
    }
375 1
376
    /** Обработчик десериализации объекта */
377 1
    public function __wakeup()
378 1
    {
379
        // Fill global instances
380 2
        self::$instances[$this->id] = &$this;
381
382 2
        // Set up default view data pointer
383
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
384
385
        // Set reference to view context entry
386
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
387 3
    }
388
389
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
390 3
391
    /** Группа методов для доступа к аттрибутам в виде массива */
392
    public function offsetSet($value, $offset)
393 3
    {
394 2
        $this->__set($offset, $value);
395 2
    }
396 1
397
    public function offsetGet($offset)
398
    {
399
        return $this->__get($offset);
400 2
    }
401
402
    // Магический метод для установки переменных представления модуля
403 3
404
    public function __get($field)
405
    {
406 3
        // Установим пустышку как значение переменной
407 1
        $result = null;
408
409
        // Если указанная переменная представления существует - получим её значение
410
        if (isset($this->data[$field])) {
411 1
            $result = &$this->data[$field];
412 1
        } else {
413 1
            throw(new ViewVariableNotFound($field));
414 1
        }
415 1
416 1
        // Иначе вернем пустышку
417 1
        return $result;
418 3
    }
419
420 3
    public function __set($value, $field = null)
421 3
    {
422
        if (is_object($field) || is_array($field)) {
423 1
            $tempValue = $field;
424
            $field = $value;
425 1
            $value = $tempValue;
426 1
427
            // TODO: Will be added in next major version
428 2
            //throw new \Exception('ViewInterface::set($value, $name) has changed, first arg is variable second is name or prefix');
429
        }
430 2
431
		// This is object
432
		if (is_object($value) && is_a($value, 'samsonframework\core\RenderInterface')) {
433
			$this->_setObject($value, $field);
434 1
		} elseif (is_array($value)) { // If array is passed
435
			$this->_setArray($value, $field);
436 1
		} else { // Set view variable
437
            $this->data[$field] = $value;
438
		}
439
    }
440
441
    public function offsetUnset($offset)
442
    {
443
        unset($this->data[$offset]);
444
    }
445
446 1
    public function offsetExists($offset)
447
    {
448
        return isset($this->data[$offset]);
449 1
    }
450 1
451 1
    /** Sort array by string length */
452
    protected function sortStrings($a, $b)
453
    {
454 1
        return strlen($b) - strlen($a);
455
    }
456
457 1
    /**
458
     * Create unique module cache folder structure in local web-application.
459 1
     *
460 1
     * @param string $file Path to file relative to module cache location
461 1
     * @param boolean $clear Flag to perform generic cache folder clearence
462
     * @return boolean TRUE if cache file has to be regenerated
463
     */
464 1
    protected function cache_refresh(&$file, $clear = true, $folder = null)
0 ignored issues
show
Coding Style introduced by
Method name "Module::cache_refresh" is not in camel caps format
Loading history...
465
    {
466
        // Add slash to end
467
        $folder = isset($folder) ? rtrim($folder, '/') . '/' : '';
468
469
        $path = $this->cache_path . $folder;
470
471
        // If module cache folder does not exists - create it
472
        if (!file_exists($path)) {
473
            mkdir($path, 0777, true);
474
        }
475 1
476
        // Build full path to cached file
477
        $file = $path . $file;
478 1
479
        // If cached file does not exsits
480
        if (!file_exists($file)) {
481 1
            // If clearence flag set to true - clear all files in module cache directory with same extension
482
            if ($clear) {
483
                File::clear($path, pathinfo($file, PATHINFO_EXTENSION));
484 1
            }
485
486
            // Signal for cache file regeneration
487 1
            return true;
488 1
        }
489
490
        return false;
491
    }
492
493
    /**
494
     *
495 1
     * @param unknown $object
496
     * @param string $viewprefix
497
     */
498 1
    private function _setObject($object, $viewprefix = null)
499
    {
500
        // Generate viewprefix as only lowercase classname without NS if it is not specified
501 1
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(ns_classname(get_class($object)), 'UTF-8');
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::className() and pass full class name to it without splitting into class name and namespace

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
502 1
503
        // Save object to view data
504
        $this->data[$class_name] = $object;
505
506
        // Add separator
507
        $class_name .= '_';
508
509
        // Generate objects view array data and merge it with view data
510
        $this->data = array_merge($this->data, $object->toView($class_name));
511
    }
512
513
    /**
514
     *
515
     * @param unknown $array
516
     * @param string $viewprefix
517
     */
518
    private function _setArray($array, $viewprefix = null)
519
    {
520
        // Save array to view data
521
        $this->data[$viewprefix] = $array;
522
523
        // Add array values to view data
524
        $this->data = array_merge($this->data, $array);
525
    }
526
}
527