Completed
Push — master ( d635bf...d10e41 )
by Vitaly
02:50
created

Router::getAssetPathData()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 14
c 1
b 0
f 0
nc 14
nop 2
dl 0
loc 20
rs 7.7777
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
        // Iterate all types of assets
78
        foreach ($this->types as $type) {
79
            $this->createAssets($paths, $type);
80
        }
81
82
        // Subscribe to core template rendering event
83
        Event::subscribe('core.rendered', [$this, 'renderTemplate']);
84
    }
85
86
    /**
87
     * Get path static resources list filtered by extensions.
88
     *
89
     * @param array $path Paths for static resources scanning
0 ignored issues
show
Bug introduced by
There is no parameter named $path. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

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