Completed
Push — master ( d10e41...c14408 )
by Vitaly
02:58
created

Router   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 24
Bugs 3 Features 15
Metric Value
c 24
b 3
f 15
dl 0
loc 212
rs 10
wmc 22
lcom 1
cbo 3

5 Methods

Rating   Name   Duplication   Size   Complexity  
B init() 0 28 3
B getAssetPathData() 0 20 8
B scanFolderRecursively() 0 28 1
B createAssets() 0 52 6
A renderTemplate() 0 19 4
1
<?php
2
namespace samsonphp\resource;
3
4
use Aws\CloudFront\Exception\Exception;
5
use samson\core\ExternalModule;
6
use samson\core\Module;
7
use samsonframework\resource\ResourceMap;
8
use samsonphp\event\Event;
9
use samsonphp\resource\exception\ResourceNotFound;
10
11
/**
12
 * Resource router for serving static resource from unreachable web-root paths.
13
 *
14
 * @author Vitaly Iegorov <[email protected]>
15
 * @author Nikita Kotenko <[email protected]>
16
 */
17
class Router extends ExternalModule
18
{
19
    /** @deprecated Use E_MODULES */
20
    const EVENT_START_GENERATE_RESOURCES = 'resourcer.modulelist';
21
    /** Event for modifying modules */
22
    const E_MODULES = 'resourcer.modulelist';
23
    /** Event for resources preloading */
24
    const E_RESOURCE_PRELOAD = 'resourcer.preload';
25
    /** Event for resources compiling */
26
    const E_RESOURCE_COMPILE = 'resourcer.compile';
27
28
    /** Collection of excluding scanning folder patterns */
29
    const EXCLUDING_FOLDERS = [
30
        '*/cache/*',
31
        '*/tests/*',
32
        '*/vendor/*/vendor/*'
33
    ];
34
35
    /** Collection of registered resource types */
36
    protected $types = ['less', 'css', 'js', 'coffee', 'ts'];
37
38
    /** @var array Assets cache */
39
    protected $cache = [];
40
41
    /** @var array Template markers for inserting assets */
42
    protected $templateMarkers = [
43
        'css' => '</head>',
44
        'js' => '</body>'
45
    ];
46
47
    /** @var array Collection of static resources */
48
    protected $resources = [];
49
    /** @var array Collection of static resource URLs */
50
    protected $resourceUrls = [];
51
52
    /** Identifier */
53
    protected $id = STATIC_RESOURCE_HANDLER;
54
55
    /** @see ModuleConnector::init() */
56
    public function init(array $params = array())
57
    {
58
        // Subscribe for CSS handling
59
        Event::subscribe(self::E_RESOURCE_COMPILE, [new CSS(), 'compile']);
60
61
        $moduleList = $this->system->module_stack;
0 ignored issues
show
Bug introduced by
Accessing module_stack on the interface samsonframework\core\SystemInterface 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...
62
        $paths = [];
63
64
        // Event for modification of module list
65
        Event::fire(self::E_MODULES, array(&$moduleList));
66
67
        $projectRoot = dirname(getcwd()).'/';
68
69
        // Add module paths
70
        foreach ($moduleList as $module) {
71
            if ($module->path() !== $projectRoot) {
72
                $paths[] = $module->path();
73
            }
74
        }
75
        $paths[] = getcwd();
76
77
        $files = $this->scanFolderRecursively($paths, $this->types);
78
79
        $this->createAssets($files);
80
81
        // Subscribe to core template rendering event
82
        Event::subscribe('core.rendered', [$this, 'renderTemplate']);
83
    }
84
85
    private function getAssetPathData($resource, $extension = null)
86
    {
87
        $extension = $extension === null ? pathinfo($resource, PATHINFO_EXTENSION) : $extension;
88
        switch ($extension) {
89
            case 'css':
90
            case 'less':
91
            case 'scss':
92
            case 'sass': $extension = 'css'; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
93
            case 'ts':
94
            case 'cofee': $extension = 'js'; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
95
        }
96
97
        $wwwRoot = getcwd();
98
        $projectRoot = dirname($wwwRoot).'/';
99
        $relativePath = str_replace($projectRoot, '', $resource);
100
101
        $fileName = pathinfo($resource, PATHINFO_FILENAME);
102
103
        return dirname($this->cache_path.$relativePath).'/'.$fileName.'.'.$extension;
104
    }
105
106
    /**
107
     * Get path static resources list filtered by extensions.
108
     *
109
     * @param array $paths Paths for static resources scanning
110
     * @param array $extensions Resource type
111
     *
112
     * @return array Matched static resources collection with full paths
113
     */
114
    protected function scanFolderRecursively(array $paths, $extensions)
115
    {
116
        // TODO: Handle not supported cmd command(Windows)
117
        // TODO: Handle not supported exec()
118
119
        // Generate LINUX command to gather resources as this is 20 times faster
120
        $files = [];
121
122
        $excludeFolders = implode(' ', array_map(function ($value) {
123
            return '-not -path ' . $value.' ';
124
        }, self::EXCLUDING_FOLDERS));
125
126
        // Get first type
127
        $firstType = array_shift($extensions);
128
129
        // Generate other types
130
        $types = implode(' ', array_map(function ($value) use ($excludeFolders){
0 ignored issues
show
Coding Style introduced by
Expected 1 space after closing parenthesis; found 0
Loading history...
131
            return '-o -name "*.' . $value . '" '.$excludeFolders;
132
        }, $extensions));
133
134
        $command = 'find ' . implode(' ', $paths) . ' -type f -name "*.' . $firstType . '" '.$excludeFolders.$types;
135
136
        // Scan path excluding folder patterns
137
        exec($command, $files);
138
139
        // TODO: Why some paths have double slashes? Investigate speed of realpath, maybe // changing if quicker
140
        return array_map('realpath', $files);
141
    }
142
143
    /**
144
     * Create static assets.
145
     *
146
     * @param array  $files Collection of paths for gathering resources
147
     */
148
    public function createAssets(array $files)
149
    {
150
        $wwwRoot = getcwd();
151
152
        $assets = [];
153
154
        // Scan folder and gather
155
        foreach ($files as $file) {
156
            // Generate cached resource path with possible new extension after compiling
157
            $assets[$file] = $this->getAssetPathData($file);
158
            $extension = pathinfo($assets[$file], PATHINFO_EXTENSION);
159
160
            // If cached assets was modified or new
161
            if (!file_exists($assets[$file]) || filemtime($file) !== filemtime($assets[$file])) {
162
                // Read asset content
163
                $this->cache[$file] = file_get_contents($file);
164
165
                // Fire event for analyzing resource
166
                Event::fire(self::E_RESOURCE_PRELOAD, [$file, pathinfo($file, PATHINFO_EXTENSION), &$this->cache[$file]]);
167
            } else {
168
                // Add this resource to resource collection grouped by resource type
169
                $this->resources[$extension][] = $assets[$file];
170
                $this->resourceUrls[$extension][] = str_replace($wwwRoot, '', $assets[$file]);
171
            }
172
        }
173
174
        $wwwRoot = getcwd();
175
        foreach ($this->cache as $file => $content) {
176
            $extension = pathinfo($file, PATHINFO_EXTENSION);
177
178
            $compiled = $content;
179
            Event::fire(self::E_RESOURCE_COMPILE, [$file, &$extension, &$compiled]);
180
181
            // Create folder structure and file only if it is not empty
182
            $resource = $this->getAssetPathData($file, $extension);
183
184
            // Create cache path
185
            $path = dirname($resource);
186
            if (!file_exists($path)) {
187
                mkdir($path, 0777, true);
188
            }
189
190
            file_put_contents($resource, $compiled);
191
192
            // Sync cached file with source file
193
            touch($resource, filemtime($file));
194
195
            // Add this resource to resource collection grouped by resource type
196
            $this->resources[$extension][] = $resource;
197
            $this->resourceUrls[$extension][] = str_replace($wwwRoot, '', $resource);
198
        }
199
    }
200
201
    /**
202
     * Template rendering handler by injecting static assets url
203
     * in appropriate.
204
     *
205
     * @param $view
206
     *
207
     * @return mixed
208
     */
209
    public function renderTemplate(&$view)
210
    {
211
        foreach ($this->resourceUrls as $type => $urls) {
212
            // Replace template marker by type with collection of links to resources of this type
213
            $view = str_ireplace(
214
                $this->templateMarkers[$type],
215
                implode("\n", array_map(function($value) use ($type) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
216
                    if ($type === 'css') {
217
                        return '<link type="text/css" rel="stylesheet" property="stylesheet" href="' . $value . '">';
218
                    } elseif ($type === 'js') {
219
                        return '<script async type="text/javascript" src="' . $value . '"></script>';
220
                    }
221
                }, $urls)) . "\n" . $this->templateMarkers[$type],
222
                $view
223
            );
224
        }
225
226
        return $view;
227
    }
228
}
229