1
|
|
|
<?php |
2
|
|
|
namespace samsonphp\compressor; |
3
|
|
|
|
4
|
|
|
use samson\core\Core; |
|
|
|
|
5
|
|
|
use samson\core\iModule; |
6
|
|
|
use samsonframework\localfilemanager\LocalFileManager; |
7
|
|
|
use samsonframework\resource\ResourceMap; |
8
|
|
|
use samsonos\compressor\Module; |
9
|
|
|
use samsonphp\compressor\resource\JavaScript; |
10
|
|
|
use samsonphp\event\Event; |
11
|
|
|
use samsonphp\resource\Router; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Module for automatic code optimization|compression |
15
|
|
|
* |
16
|
|
|
* @package samsonos\compressor |
17
|
|
|
* @author Vitaly Iegorov <[email protected]> |
18
|
|
|
*/ |
19
|
|
|
class Compressor |
20
|
|
|
{ |
21
|
|
|
/** Identifier of global namespace */ |
22
|
|
|
const NS_GLOBAL = ''; |
23
|
|
|
|
24
|
|
|
const E_CREATE_MODULE_LIST = 'compressor.create.module.list'; |
25
|
|
|
|
26
|
|
|
const E_CREATE_RESOURCE_LIST = 'compressor.create.resource.list'; |
27
|
|
|
|
28
|
|
|
const E_RESOURCE_COMPRESS = 'compressor.resource.compress'; |
29
|
|
|
|
30
|
|
|
/** Array key for storing last generated data */ |
31
|
|
|
const VIEWS = 'views'; |
32
|
|
|
|
33
|
|
|
/** Output path for compressed web application */ |
34
|
|
|
public $output = 'out/'; |
35
|
|
|
|
36
|
|
|
/** Collection of requires to insert in compressed file */ |
37
|
|
|
public $require = array(); |
38
|
|
|
|
39
|
|
|
/** Ignored resource extensions */ |
40
|
|
|
public $ignored_extensions = array('php', 'js', 'css', 'md', 'map', 'dbs', 'vphp', 'less', 'gz', 'lock', 'json', 'sql', 'xml', 'yml'); |
41
|
|
|
|
42
|
|
|
/** Ignored resource files */ |
43
|
|
|
public $ignored_resources = array('.project', '.buildpath', '.gitignore', '.travis.yml', 'phpunit.xml', 'thumbs.db', 'Thumbs.db'); |
44
|
|
|
|
45
|
|
|
/** @var array Collection of folders to be ignored by compressor */ |
46
|
|
|
public $ignoredFolders = array('vendor', 'var'); |
47
|
|
|
|
48
|
|
|
/** @var array Collection of file paths to be ignored by compressor */ |
49
|
|
|
public $ignoredFiles = array(); |
50
|
|
|
|
51
|
|
|
/** Папка где размещается исходное веб-приложение */ |
52
|
|
|
public $input = __SAMSON_CWD__; |
53
|
|
|
|
54
|
|
|
/** View rendering mode */ |
55
|
|
|
protected $view_mode = Core::RENDER_VARIABLE; |
|
|
|
|
56
|
|
|
|
57
|
|
|
/** Указатель на текущий сворачиваемый модуль */ |
58
|
|
|
protected $current; |
59
|
|
|
|
60
|
|
|
/** Коллекция уже обработанных файлов */ |
61
|
|
|
protected $files = array(); |
62
|
|
|
|
63
|
|
|
/** Collection for storing all php code by namespace */ |
64
|
|
|
private $php = array(self::NS_GLOBAL => array()); |
65
|
|
|
|
66
|
|
|
/** @var string Web-application environment identifier */ |
67
|
|
|
protected $environment = 'prod'; |
68
|
|
|
|
69
|
|
|
/** @var bool Debug flag */ |
70
|
|
|
protected $debug = false; |
71
|
|
|
|
72
|
|
|
/** @var string Supported php version */ |
73
|
|
|
protected $phpVersion = PHP_VERSION; |
74
|
|
|
|
75
|
|
|
protected $resourceManager; |
76
|
|
|
|
77
|
|
|
protected $classConst = array(); |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
protected $resourceUrlsList = []; |
81
|
|
|
|
82
|
|
|
/** @var FileManagerInterface File system manager */ |
83
|
|
|
protected $fileManager; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Compress web-application |
87
|
|
|
* @param string $output Path for creating compressed version |
88
|
|
|
* @param boolean $debug Disable errors output |
89
|
|
|
* @param string $environment Configuration environment |
90
|
|
|
* @param string $phpVersion PHP version to support |
91
|
|
|
* @param array $configuration Configuration |
92
|
|
|
*/ |
93
|
|
|
public function __construct($output = 'out/', $debug = false, $environment = 'prod', $phpVersion = PHP_VERSION, $configuration = array()) |
94
|
|
|
{ |
95
|
|
|
$this->resourceManager = new resource\Generic($this); |
96
|
|
|
|
97
|
|
|
$this->fileManager = new LocalFileManager(); |
|
|
|
|
98
|
|
|
|
99
|
|
|
$this->output = $output; |
100
|
|
|
$this->debug = $debug; |
101
|
|
|
$this->environment = $environment; |
102
|
|
|
$this->phpVersion = $phpVersion; |
103
|
|
|
foreach ($configuration as $key => $value) { |
104
|
|
|
// If object has configured property defined |
105
|
|
|
if (property_exists($this, $key)) { |
106
|
|
|
// Set object variable value |
107
|
|
|
$this->$key = $value; |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Свернуть файл представления |
114
|
|
|
* |
115
|
|
|
* @param string $view_file Полный путь к файлу представления |
116
|
|
|
* @param iModule $module Указатель на модуль которому принадлежит это представление |
117
|
|
|
* |
118
|
|
|
* @return bool |
119
|
|
|
*/ |
120
|
|
|
public function compress_view($view_file, iModule & $module) |
|
|
|
|
121
|
|
|
{ |
122
|
|
|
// Build relative path to module view |
123
|
|
|
$rel_path = ($module->id() == 'local' ? '' : $module->id() . '/') . str_replace($module->path(), '', $view_file); |
124
|
|
|
|
125
|
|
|
$this->log(' -- Preparing view[##] relative path [##]', $view_file, $rel_path); |
126
|
|
|
|
127
|
|
|
// Прочитаем файл представления |
128
|
|
|
$view_html = file_get_contents($view_file); |
129
|
|
|
|
130
|
|
|
if (!isset($view_file{0})) return e('View: ##(##) is empty', E_SAMSON_SNAPSHOT_ERROR, array($view_file, $rel_path)); |
|
|
|
|
131
|
|
|
|
132
|
|
|
// TODO: should be done via events in resourcer module |
133
|
|
|
// Найдем обращения к роутеру ресурсов |
134
|
|
|
$view_html = preg_replace_callback( |
135
|
|
|
'/(<\?php)*\s*src\s*\(\s*(\'|\")?(?<path>[^\'\"\?\;\)]+)(\'|\")?(\s*,\s*(\'|\")(?<module>[^\'\"\)]+)(\'|\"))?\s*\)\;?(\s*\?>)?/uis', |
136
|
|
|
array($this, 'src_replace_callback'), |
137
|
|
|
$view_html |
138
|
|
|
); |
139
|
|
|
|
140
|
|
|
// Replace old inline php tags |
141
|
|
|
$view_html = str_ireplace('<? ', '<?php ', $view_html); |
142
|
|
|
|
143
|
|
|
// Сожмем HTML |
144
|
|
|
$view_html = Minify_HTML::minify($view_html); |
145
|
|
|
|
146
|
|
|
// Fire event to render view correctly |
147
|
|
|
Event::fire('core.render', array(&$view_html, array(), &$module)); |
148
|
|
|
|
149
|
|
|
// Template re-rendering |
150
|
|
|
// TODO: We must split regular view and template file to handle differently, for now nothing will change but in future.... |
151
|
|
|
|
152
|
|
|
$template = !isset($this->resourceUrlsList[$view_file])?Router::I_MAIN_PROJECT_TEMPLATE:$view_file; |
153
|
|
|
|
154
|
|
|
Event::fire('core.rendered', array(&$view_html, $this->resourceUrlsList[$template])); |
155
|
|
|
|
156
|
|
|
$view_php = "<<<'EOT'" . "\n" . $view_html . "\n" . "EOT;"; |
157
|
|
|
|
158
|
|
|
// Add view code to final global namespace |
159
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . '$GLOBALS["__compressor_files"]["' . $rel_path . '"] = ' . $view_php; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Свернуть модуль |
164
|
|
|
* |
165
|
|
|
* @param iModule $module Указатель на модуль для сворачивания |
166
|
|
|
* @param ResourceMap $data |
167
|
|
|
*/ |
168
|
|
|
public function compress_module(iModule &$module, ResourceMap &$data) |
|
|
|
|
169
|
|
|
{ |
170
|
|
|
// Идентификатор модуля |
171
|
|
|
$id = $module->id(); |
172
|
|
|
|
173
|
|
|
// Сохраним указатель на текущий модуль |
174
|
|
|
$this->current = &$module; |
175
|
|
|
|
176
|
|
|
// Build output module path |
177
|
|
|
$module_output_path = $id == 'local' ? '' : basename($module->path()) . '/'; |
178
|
|
|
|
179
|
|
|
// Build resource source path |
180
|
|
|
$module_path = $id == 'local' ? $module->path() . __SAMSON_PUBLIC_PATH : $module->path(); |
181
|
|
|
|
182
|
|
|
$this->log(' - Compressing module[##] from [##]', $id, $module_path); |
183
|
|
|
|
184
|
|
|
// Call special method enabling module personal resource pre-management on compressing |
185
|
|
|
if ($module->beforeCompress($this, $this->php) !== false) { |
186
|
|
|
// Copy all module resources |
187
|
|
|
$this->copy_path_resources($data->resources, $module_path, $module_output_path); |
188
|
|
|
|
189
|
|
|
// Internal collection of module php code, not views |
190
|
|
|
$module_php = array(); |
191
|
|
|
|
192
|
|
|
foreach ($data->classes as $key => $php) { |
193
|
|
|
$this->compress_php($key, $module, $module_php); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// Iterate module plain php code |
197
|
|
|
foreach ($data->php as $php) { |
198
|
|
|
$this->compress_php($php, $module, $module_php); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
foreach ($data->globals as $php) { |
202
|
|
|
$this->compress_php($php, $module, $module_php); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
// Iterate module controllers php code |
206
|
|
|
foreach ($data->controllers as $php) { |
207
|
|
|
$this->compress_php($php, $module, $module_php); |
208
|
|
|
} |
209
|
|
|
// Iterate module controllers php code |
210
|
|
|
foreach ($data->modules as $php) { |
211
|
|
|
$this->compress_php($php[1], $module, $module_php); |
212
|
|
|
} |
213
|
|
|
// Iterate module model php code |
214
|
|
|
foreach ($data->models as $php) { |
215
|
|
|
$this->compress_php($php, $module, $module_php); |
216
|
|
|
} |
217
|
|
|
// Iterate module views |
218
|
|
|
foreach ($data->views as $php) { |
219
|
|
|
$this->compress_view($php, $module); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// Call special method enabling module personal resource post-management on compressing |
224
|
|
|
$module->afterCompress($this, $module_php); |
|
|
|
|
225
|
|
|
|
226
|
|
|
// Gather all code in to global code collection with namespaces |
227
|
|
|
$this->code_array_combine($module_php, $this->php); |
228
|
|
|
|
229
|
|
|
// Change module path |
230
|
|
|
$module->path($id . '/'); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
public function rewriteResourceRouter(array $matches) |
234
|
|
|
{ |
235
|
|
|
//trace($matches,1); |
236
|
|
|
//die; |
237
|
|
|
return '"' . $this->current->id() . '/' . $matches['path'] . '";'; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Обработчик замены роутера ресурсов |
242
|
|
|
* @param array $matches Найденые совпадения по шаблону |
243
|
|
|
* @return string Обработанный вариант пути к ресурсу |
244
|
|
|
*/ |
245
|
|
|
public function src_replace_callback($matches) |
|
|
|
|
246
|
|
|
{ |
247
|
|
|
// Получим относительный путь к ресурсу |
248
|
|
|
$path = trim($matches['path']); |
249
|
|
|
|
250
|
|
|
// Путь к модуля после сжимания |
251
|
|
|
$module_path = (isset($matches['module']) && strlen($matches['module']) > 0) ? $matches['module'] . '/' : $this->current->id() . '/'; |
252
|
|
|
|
253
|
|
|
// Если передана переменная мы не можем гарантировать её значение |
254
|
|
|
if (strpos($path, '$') !== false) $path = '<?php echo \'' . $module_path . '\'.' . $path . '; ?>'; |
|
|
|
|
255
|
|
|
// Просто строка |
256
|
|
|
else $path = $module_path . $path; |
|
|
|
|
257
|
|
|
|
258
|
|
|
return $path; |
259
|
|
|
//e('Файл представления ## - Обращение к роутеру ресурсов через переменную ##', E_SAMSON_SNAPSHOT_ERROR, array($view_path, $path)); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** Generic log function for further modification */ |
263
|
|
|
public function log($message) |
264
|
|
|
{ |
265
|
|
|
// Get passed vars |
266
|
|
|
$vars = func_get_args(); |
267
|
|
|
// Remove first message var |
268
|
|
|
array_shift($vars); |
269
|
|
|
|
270
|
|
|
// Render debug message |
271
|
|
|
return trace(debug_parse_markers($message, $vars)); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Compress web-application |
276
|
|
|
* @param boolean $debug Disable errors output |
277
|
|
|
* @param string $php_version PHP version to support |
278
|
|
|
*/ |
279
|
|
|
public function compress($debug = false, $environment = 'prod', $php_version = PHP_VERSION) |
280
|
|
|
{ |
281
|
|
|
// Set compressed project environment |
282
|
|
|
$this->environment = $environment; |
283
|
|
|
|
284
|
|
|
elapsed('Started web-application compression[' . $this->environment . ']'); |
285
|
|
|
|
286
|
|
|
s()->async(true); |
|
|
|
|
287
|
|
|
ini_set('memory_limit', '256M'); |
288
|
|
|
|
289
|
|
|
// Check output path |
290
|
|
|
if (!isset($this->output{0})) { |
291
|
|
|
return $this->log('Cannot compress web-application from [##] - No output path is specified', $this->input); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// Define rendering model depending on PHP version |
295
|
|
|
$php_version = isset($php_version{0}) ? $php_version : PHP_VERSION; |
296
|
|
|
if (version_compare($php_version, '5.3.0', '<')) { |
297
|
|
|
$this->view_mode = Core::RENDER_ARRAY; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
// Add url base to path |
301
|
|
|
$this->output .= url()->base(); |
302
|
|
|
|
303
|
|
|
// Creating output project folder |
304
|
|
|
$result = \samson\core\File::mkdir($this->output); |
305
|
|
|
if ($result) { |
306
|
|
|
$this->log('Created output project folder [##]', $this->output); |
307
|
|
|
} else if ($result == -1) { |
308
|
|
|
return $this->log('Compression failed! Cannot create output project folder [##]', $this->output); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
// Remove all trailing slashes |
312
|
|
|
$this->output = realpath($this->output) . '/'; |
313
|
|
|
|
314
|
|
|
$this->log('[##]## Compressing web-application[##] from [##] to [##]', |
315
|
|
|
$environment, |
316
|
|
|
$debug ? '[DEBUG]' : '', |
317
|
|
|
$php_version, |
318
|
|
|
$this->input, |
319
|
|
|
$this->output |
320
|
|
|
); |
321
|
|
|
|
322
|
|
|
// Add generic composer auto loader require |
323
|
|
|
$this->php['__before_all']['composer'] = "\n" . 'if(file_exists("vendor/autoload.php")) require "vendor/autoload.php";'; |
324
|
|
|
|
325
|
|
|
// Define global views collection |
326
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] = "\n" . '$GLOBALS["__compressor_files"] = array();'; |
327
|
|
|
|
328
|
|
|
// If resourcer is loaded - copy css and js |
329
|
|
|
// Link |
330
|
|
|
$rr = &s()->module_stack['resourcer']; |
|
|
|
|
331
|
|
|
|
332
|
|
|
// Iterate all css and js resources |
333
|
|
|
$ignoreFolders = array(); |
334
|
|
|
foreach ($this->ignoredFolders as $folder) { |
335
|
|
|
$ignoreFolders[] = $this->output . $folder; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
// Remove all old javascript and css |
339
|
|
|
\samson\core\File::clear($this->output, array('js', 'css'), $ignoreFolders); |
340
|
|
|
|
341
|
|
|
$moduleListArray = []; |
342
|
|
|
|
343
|
|
|
//$moduleListArray[Router::I_MAIN_PROJECT_TEMPLATE] = $this->system->module_stack; |
344
|
|
|
|
345
|
|
|
Event::fire(self::E_CREATE_MODULE_LIST, array(& $moduleListArray)); |
346
|
|
|
|
347
|
|
|
$resource = new Resource($this->fileManager); |
348
|
|
|
|
349
|
|
|
foreach ($moduleListArray as $template => $moduleList) |
350
|
|
|
{ |
351
|
|
|
$resourceUrls = []; |
352
|
|
|
|
353
|
|
|
Event::fire(self::E_CREATE_RESOURCE_LIST, array(& $resourceUrls, $moduleList)); |
354
|
|
|
|
355
|
|
|
foreach ($resourceUrls as $type => $urls) { |
356
|
|
|
$file = $resource->compress($urls, $type, $this->output); |
357
|
|
|
$this->resourceUrlsList[$template][$type] = [DIRECTORY_SEPARATOR.$file]; |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
// Iterate core ns resources collection |
362
|
|
|
foreach (s()->module_stack as $id => &$module) { |
|
|
|
|
363
|
|
|
// Work only with compressable modules |
364
|
|
|
if (is_a($module, ns_classname('CompressInterface', 'samsonframework\core')) || |
|
|
|
|
365
|
|
|
(isset($this->composerParameters['samsonphp_package_compressable']) && |
|
|
|
|
366
|
|
|
($this->composerParameters['samsonphp_package_compressable'] = 1)) |
367
|
|
|
) { |
368
|
|
|
$this->compress_module($module, $module->resourceMap); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
// Change path to local modules |
372
|
|
|
if (is_a($module, '\samson\core\VirtualModule')) { |
373
|
|
|
$module->path(''); |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
|
378
|
|
|
|
379
|
|
|
|
380
|
|
|
|
381
|
|
|
|
382
|
|
|
|
383
|
|
|
|
384
|
|
|
/*foreach ($rr->cached['js'] as $jsCachedFile) { |
385
|
|
|
// Manage javascript resource |
386
|
|
|
$javascriptManager = new resource\JavaScript($this); |
387
|
|
|
$javascriptManager->compress(__SAMSON_CWD__ . $jsCachedFile, $this->output . basename($jsCachedFile)); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
foreach ($rr->cached['css'] as $cssCachedFile) { |
391
|
|
|
// Manage CSS resource |
392
|
|
|
$cssManager = new resource\CSS($this, $rr); |
393
|
|
|
$cssManager->compress(__SAMSON_CWD__ . $cssCachedFile, $this->output . basename($cssCachedFile)); |
394
|
|
|
}*/ |
395
|
|
|
//} |
396
|
|
|
|
397
|
|
|
// Copy main project composer.json |
398
|
|
|
$composerPath = __SAMSON_CWD__ . 'composer.json'; |
399
|
|
|
if (file_exists($composerPath)) { |
400
|
|
|
// Read json file |
401
|
|
|
$composerJSON = (array)json_decode(file_get_contents($composerPath)); |
402
|
|
|
// Remove development dependencies |
403
|
|
|
unset($composerJSON['require-dev']); |
404
|
|
|
// Remove autoload section |
405
|
|
|
unset($composerJSON['autoload']); |
406
|
|
|
// Remove install/update scripts |
407
|
|
|
unset($composerJSON['scripts']); |
408
|
|
|
|
409
|
|
|
// Write modified composer.json |
410
|
|
|
file_put_contents($this->output . 'composer.json', json_encode($composerJSON)); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
// Set errors output |
414
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . '\samson\core\Error::$OUTPUT = ' . (!$debug ? 'false' : 'true') . ';'; |
415
|
|
|
|
416
|
|
|
// Create SamsonPHP core compressor |
417
|
|
|
$core = new \samsonphp\compressor\Core(s(), $environment, $this); |
|
|
|
|
418
|
|
|
|
419
|
|
|
// Add global base64 serialized core string |
420
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . '$GLOBALS["__CORE_SNAPSHOT"] = \'' . $core->compress() . '\';'; |
421
|
|
|
|
422
|
|
|
// Add all specified requires |
423
|
|
|
foreach ($this->require as $require) $this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . 'require("' . $require . '");'; |
|
|
|
|
424
|
|
|
|
425
|
|
|
// Add localization data |
426
|
|
|
$locale_str = array(); |
427
|
|
|
foreach (\samson\core\SamsonLocale::$locales as $locale) { |
428
|
|
|
if ($locale != '') { |
429
|
|
|
$locale_str[] = '\'' . $locale . '\''; |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
// Add [setlocales] code |
433
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . 'setlocales( ' . implode(',', $locale_str) . ');'; |
434
|
|
|
|
435
|
|
|
// TODO: add generic handlers to modules to provide compressing logic for each module |
436
|
|
|
// TODO: add generic constants namespace to put all constants definition there - and put only defined constrat and redeclare them |
437
|
|
|
|
438
|
|
|
// TODO: WTF???? Thi must be local module logic |
439
|
|
|
// If this is remote web-app - collect local resources |
440
|
|
|
if (__SAMSON_REMOTE_APP) { |
|
|
|
|
441
|
|
|
// Gather all resources |
442
|
|
|
$path = __SAMSON_CWD__; |
443
|
|
|
$ls = array(); |
444
|
|
|
s()->resources($path, $ls); |
|
|
|
|
445
|
|
|
|
446
|
|
|
// If we have any resources |
447
|
|
|
if (isset($ls['resources'])) { |
448
|
|
|
$this->copy_path_resources($ls['resources'], __SAMSON_CWD__, ''); |
449
|
|
|
} |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
// If default locale is defined |
453
|
|
|
if (!defined('DEFAULT_LOCALE')) { |
454
|
|
|
define('DEFAULT_LOCALE', 'ru'); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
// Add default system locale to them end of core definition |
458
|
|
|
$this->php['samson\core'][self::VIEWS] = "\n" . 'define("DEFAULT_LOCALE", "' . DEFAULT_LOCALE . '");'; |
459
|
|
|
|
460
|
|
|
// Pointer to entry script code |
461
|
|
|
$entryScriptPath = __SAMSON_CWD__ . __SAMSON_PUBLIC_PATH . 'index.php'; |
462
|
|
|
|
463
|
|
|
$entryScript = &$this->php[self::NS_GLOBAL][$entryScriptPath]; |
464
|
|
|
|
465
|
|
|
// Collect all event system data |
466
|
|
|
$eventCompressor = new EventCompressor(); |
467
|
|
|
$eventCompressor->collect($entryScript); |
468
|
|
|
|
469
|
|
|
// Remove standard framework entry point from index.php - just preserve default controller |
470
|
|
|
if (preg_match('/start\(\s*(\'|\")(?<default>[^\'\"]+)/i', $entryScript, $matches)) { |
471
|
|
|
/* |
472
|
|
|
* Temporary solution to support compressed version, because other way localization does not work, |
473
|
|
|
* as chain is broken, first time URL object is created and URL is parsed only after start, so |
474
|
|
|
* CMS::afterCompress does not knows what is current locale and does not inject it to all material |
475
|
|
|
* queries. |
476
|
|
|
*/ |
477
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . 'url();'; |
478
|
|
|
|
479
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] .= "\n" . 's()->start(\'' . $matches['default'] . '\');'; |
480
|
|
|
} else e('Default module definition not found - possible errors at compressed version'); |
|
|
|
|
481
|
|
|
|
482
|
|
|
// Clear default entry point |
483
|
|
|
unset($this->php[self::NS_GLOBAL][$entryScriptPath]); |
484
|
|
|
|
485
|
|
|
// Set global namespace as last |
486
|
|
|
$global_ns = $this->php[self::NS_GLOBAL]; |
487
|
|
|
unset($this->php[self::NS_GLOBAL]); |
488
|
|
|
$this->php[self::NS_GLOBAL] = $global_ns; |
489
|
|
|
|
490
|
|
|
// Set view data to the end of global namespace |
491
|
|
|
$s = $this->php[self::NS_GLOBAL][self::VIEWS]; |
492
|
|
|
unset($this->php[self::NS_GLOBAL][self::VIEWS]); |
493
|
|
|
$this->php[self::NS_GLOBAL][self::VIEWS] = $s; |
494
|
|
|
|
495
|
|
|
// Load all OOP entities |
496
|
|
|
$classes = array(); |
497
|
|
|
// Соберем коллекцию загруженных интерфейсов их файлов по пространствам имен |
498
|
|
|
$this->classes_to_ns_files(get_declared_interfaces(), $classes); |
499
|
|
|
|
500
|
|
|
// Соберем коллекцию загруженных классов их файлов по пространствам имен |
501
|
|
|
$this->classes_to_ns_files(get_declared_classes(), $classes); |
502
|
|
|
|
503
|
|
|
// Fix OOP entities |
504
|
|
|
foreach ($this->php as $ns => & $files) { |
505
|
|
|
// If this namespace has been loaded |
506
|
|
|
if (isset($classes[$ns])) { |
507
|
|
|
// Fill namespace entities, make OOP entities correct order |
508
|
|
|
$files = array_merge($classes[$ns], $files); |
509
|
|
|
} |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
// Соберем весь PHP код в один файл |
513
|
|
|
$index_php = $this->code_array_to_str($this->php, ($this->view_mode == 2)); |
514
|
|
|
|
515
|
|
|
// Collect all event system data |
516
|
|
|
$eventCompressor->collect($index_php); |
517
|
|
|
|
518
|
|
|
// Transform event system in all project code |
519
|
|
|
if ($eventCompressor->transform($index_php, $index_php)) { |
520
|
|
|
//trace($eventCompressor->subscriptions, true); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
// Remove url_base parsing and put current url base |
524
|
|
View Code Duplication |
if (preg_match('/define\(\'__SAMSON_BASE__\',\s*([^;]+)/i', $index_php, $matches)) { |
|
|
|
|
525
|
|
|
$index_php = str_replace($matches[0], 'define(\'__SAMSON_BASE__\',\'' . __SAMSON_BASE__ . '\');', $index_php); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
// Set global constant to specify supported PHP version |
529
|
|
View Code Duplication |
if (preg_match('/define\s*\(\'__SAMSON_PHP_OLD[^;]+/', $index_php, $matches)) { |
|
|
|
|
530
|
|
|
$index_php = str_replace($matches[0], 'define(\'__SAMSON_PHP_OLD\',\'' . ($this->view_mode == 2) . '\');', $index_php); |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
$index_php = $this->removeBlankLines($index_php); |
534
|
|
|
$index_php = preg_replace('/(declare *\( *strict_types *= *1 *\) *;)/i', ' ', $index_php); |
535
|
|
|
|
536
|
|
|
// Запишем пусковой файл |
537
|
|
|
file_put_contents($this->output . 'index.php', '<?php ' . $index_php . "\n" . '?>'); |
538
|
|
|
|
539
|
|
|
// Minify PHP code if no debug is needed |
540
|
|
|
if (!$debug) { |
541
|
|
|
file_put_contents($this->output . 'index.php', php_strip_whitespace($this->output . 'index.php')); |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
elapsed('Site has been successfully compressed to ' . $this->output); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Преобразовать коллекцию полученного кода в виде NS/Files в строку |
549
|
|
|
* с правильными NS |
550
|
|
|
* |
551
|
|
|
* @param array $code Коллекция кода полученная функцией @see compress_php() |
552
|
|
|
* @param boolean $no_ns Флаг убирания NS из кода |
553
|
|
|
* @return string Правильно собранный код в виде строки |
554
|
|
|
*/ |
555
|
|
|
public function code_array_to_str(array $code, $no_ns = false) |
|
|
|
|
556
|
|
|
{ |
557
|
|
|
// Соберем весь PHP код модуля |
558
|
|
|
$php_code = ''; |
559
|
|
|
foreach ($code as $ns => $files) { |
560
|
|
|
// If we support namespaces |
561
|
|
|
if (!$no_ns) $php_code .= "\n" . 'namespace ' . $ns . '{'; |
|
|
|
|
562
|
|
|
|
563
|
|
|
// Insert files code |
564
|
|
|
foreach ($files as $file => $php) { |
565
|
|
|
// Ignore uses array |
566
|
|
|
if ($file == 'uses') continue; |
|
|
|
|
567
|
|
|
// TODO: Add uses support class name changing |
568
|
|
|
|
569
|
|
|
// If we does not support namespaces |
570
|
|
|
if ($no_ns) { |
571
|
|
|
// Find all static class usage |
572
|
|
|
if (preg_match_all('/[\!\.\,\(\s\n\=\:]+\s*(?:self|parent|static|(?<classname>[\\\a-z_0-9]+))::/i', $php, $matches)) { |
573
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
// Find all class definition |
577
|
|
|
if (preg_match_all('/(\n|\s)\s*class\s+(?<classname>[^\s]+)/i', $php, $matches)) { |
578
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
// Find all instanceof definition |
582
|
|
|
if (preg_match_all('/\s+instanceof\s+(?<classname>[\\\a-z_0-9]+)/i', $php, $matches)) { |
583
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
// Find all interface definition |
587
|
|
|
if (preg_match_all('/(\n|\s)\s*interface\s+(?<classname>[^\s]+)/i', $php, $matches)) { |
588
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
// Find all class implements, class can implement many interfaces |
592
|
|
|
if (preg_match_all('/\s+implements\s+(?<classes>.*)/i', $php, $matches)) { |
593
|
|
|
$replace = $matches[0][0]; |
594
|
|
|
foreach (explode(',', $matches['classes'][0]) as $classname) { |
595
|
|
|
$replace = $this->transformClassName($classname, $classname, $replace, $ns); |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
$php = str_replace($matches[0][0], $replace, $php); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
// Find all class extends |
602
|
|
|
if (preg_match_all('/\s+extends\s+(?<classname>[^\s]+)/i', $php, $matches)) { |
603
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
// Find all class creation |
607
|
|
|
if (preg_match_all('/[\.\,\(\s\n=:]+\s*new\s+(?<classname>[^\(]+)\s*\(/i', $php, $matches)) { |
608
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
// Find all class hints |
612
|
|
|
if (preg_match_all('/(\(|\,)\s*(?:array|(?<classname>[\\\a-z_0-9]+))\s*(\&|\$)/i', $php, $matches)) { |
613
|
|
|
$php = $this->changeClassName($matches, $php, $ns); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
// Replace special word with its value |
617
|
|
|
$php = str_replace('__NAMESPACE__', '\'' . $ns . '\'', $php); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
// Just concatenate file code |
621
|
|
|
$php_code .= $php; |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
// Close namespace if we support |
625
|
|
|
if (!$no_ns) $php_code .= "\n" . '}'; |
|
|
|
|
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
return $php_code; |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
public function code_array_combine(array & $source, array & $target) |
|
|
|
|
632
|
|
|
{ |
633
|
|
|
foreach ($source as $ns => $files) { |
634
|
|
|
// Если в целевом массиве нет нужного NS - создадим |
635
|
|
|
if (!isset($target[$ns])) $target[$ns] = array(); |
|
|
|
|
636
|
|
|
|
637
|
|
|
// Запишем содержание NS/Files |
638
|
|
|
foreach ($files as $file => $php) { |
639
|
|
|
if (isset($target[$ns][$file]) && is_string($php)) $target[$ns][$file] .= $php; |
|
|
|
|
640
|
|
|
else if (isset($target[$ns][$file]) && is_array($php)) { |
641
|
|
|
$target[$ns][$file] = array_unique(array_merge($target[$ns][$file], $php)); |
642
|
|
|
} else $target[$ns][$file] = $php; |
|
|
|
|
643
|
|
|
} |
644
|
|
|
} |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Выполнить рекурсивное "собирание" файла |
649
|
|
|
* |
650
|
|
|
* @param string $path Абсолютный путь к файлу сайта |
651
|
|
|
* |
652
|
|
|
* @param null $module |
653
|
|
|
* @param array $code |
654
|
|
|
* @param string $namespace |
655
|
|
|
* |
656
|
|
|
* @return string |
657
|
|
|
*/ |
658
|
|
|
public function compress_php($path, $module = NULL, & $code = array(), $namespace = self::NS_GLOBAL) |
|
|
|
|
659
|
|
|
{ |
660
|
|
|
// TODO: Довести до ума разпознование require - убрать точку с зяпятоц которая остается |
661
|
|
|
// TODO: Убрать пустые линии |
662
|
|
|
// TODO: Анализатор использования функция и переменных?? |
663
|
|
|
|
664
|
|
|
//trace(' + Вошли в функцию:'.$path.'('.$namespace.')'); |
665
|
|
|
$_path = $path; |
666
|
|
|
$path = normalizepath(realpath($path)); |
667
|
|
|
|
668
|
|
|
// Если мы уже подключили данный файл или он не существует |
669
|
|
View Code Duplication |
if (isset($this->files[$path])) return $this->log(' ! Файл: [##], already compressed', $path); |
|
|
|
|
670
|
|
|
else if (!is_file($path)) return $this->log(' ! Файл: [##], не существует', $_path); |
|
|
|
|
671
|
|
|
else if (strpos($path, 'vendor/autoload.php') !== false) return $this->log(' Ignoring composer autoloader [##]', $path); |
|
|
|
|
672
|
|
|
else if (in_array(basename($path), $this->ignoredFiles)) { |
673
|
|
|
return $this->log(' Ignoring file[##] by configuration', $path); |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
|
677
|
|
|
$this->log(' -- Compressing file [##]', $path); |
678
|
|
|
|
679
|
|
|
// Load file |
680
|
|
|
require_once($path); |
681
|
|
|
|
682
|
|
|
//trace('Чтение файла: '.$path ); |
683
|
|
|
|
684
|
|
|
// Сохраним файл |
685
|
|
|
$this->files[$path] = $path; |
686
|
|
|
|
687
|
|
|
// Относительный путь к файлу |
688
|
|
|
if (isset($rel_path)) $this->files[$rel_path] = $path; |
|
|
|
|
689
|
|
|
|
690
|
|
|
// Прочитаем php файл |
691
|
|
|
$fileStr = file_get_contents($path); |
692
|
|
|
|
693
|
|
|
// Если в файле нет namespace - считаем его глобальным |
694
|
|
|
if (strpos($fileStr, 'namespace') === false) |
|
|
|
|
695
|
|
|
|
696
|
|
|
//$file_dir = ''; |
697
|
|
|
// Вырежим путь к файлу |
698
|
|
|
//$file_dir = (pathinfo( $path, PATHINFO_DIRNAME ) == '.' ? '' : pathinfo( $path, PATHINFO_DIRNAME ).'/'); |
699
|
|
|
|
700
|
|
|
// Сюда соберем код программы |
701
|
|
|
$main_code = ''; |
|
|
|
|
702
|
|
|
$main_code = "\n" . '// Модуль: ' . m($module)->id() . ', файл: ' . $path . "\n"; |
|
|
|
|
703
|
|
|
|
704
|
|
|
// Создадим уникальную коллекцию алиасов для NS |
705
|
|
|
if (!isset($code[$namespace]['uses'])) $code[$namespace]['uses'] = array(); |
|
|
|
|
706
|
|
|
|
707
|
|
|
// Установим ссылку на коллекцию алиасов |
708
|
|
|
$uses = &$code[$namespace]['uses']; |
709
|
|
|
|
710
|
|
|
// Local file uses collection |
711
|
|
|
$file_uses = array(); |
712
|
|
|
|
713
|
|
|
// Получим константы документа |
714
|
|
|
$consts = get_defined_constants(); |
715
|
|
|
|
716
|
|
|
// Маркеры для отрезания специальных блоков которые не нужны в PRODUCTION |
717
|
|
|
$rmarker_st = '\/\/\[PHPCOMPRESSOR\(remove\,start\)\]'; |
718
|
|
|
$rmarker_en = '\/\/\[PHPCOMPRESSOR\(remove\,end\)\]'; |
719
|
|
|
|
720
|
|
|
// Найдем все "ненужные" блоки кода и уберем их |
721
|
|
|
$fileStr = preg_replace('/' . $rmarker_st . '.*?' . $rmarker_en . '/uis', '', $fileStr); |
722
|
|
|
|
723
|
|
|
$className = ''; |
724
|
|
|
$classConstList = array(); |
725
|
|
|
|
726
|
|
|
//TODO: Fix to normal external dependency with ResourceRouter |
727
|
|
|
$fileStr = preg_replace_callback('/(\\\\samson\\\\resourcer\\\\)?ResourceRouter::url\((\'|\")(?<path>[^,)]+)(\'|\")(,(?<module>[^)]+))?\);/i', array($this, 'rewriteResourceRouter'), $fileStr); |
728
|
|
|
|
729
|
|
|
/** @var bool $classStared Flag for matching trait uses */ |
730
|
|
|
$classStared = false; |
731
|
|
|
|
732
|
|
|
// Разберем код программы |
733
|
|
|
$tokens = token_get_all($fileStr); |
734
|
|
|
for ($i = 0; $i < sizeof($tokens); $i++) { |
|
|
|
|
735
|
|
|
// Получим следующий жетон из кода программы |
736
|
|
|
$token = $tokens[$i]; |
737
|
|
|
|
738
|
|
|
// Если просто строка |
739
|
|
|
if (is_string($token)) $main_code .= $token; |
|
|
|
|
740
|
|
|
// Если это специальный жетон |
741
|
|
|
else { |
742
|
|
|
// token array |
743
|
|
|
list($id, $text) = $token; |
744
|
|
|
|
745
|
|
|
// Перебирем тип комманды |
746
|
|
|
switch ($id) { |
747
|
|
|
case T_COMMENT: // Пропускаем все комментарии |
748
|
|
|
case T_DOC_COMMENT: |
749
|
|
|
case T_CLOSE_TAG: // Начало,конец файла |
750
|
|
|
case T_OPEN_TAG: |
751
|
|
|
break; |
752
|
|
|
|
753
|
|
|
case T_WHITESPACE: |
754
|
|
|
$main_code .= $text; /*$main_code .= ' ';*/ |
755
|
|
|
break; |
756
|
|
|
|
757
|
|
|
// Обработаем алиасы |
758
|
|
|
case T_USE: |
759
|
|
|
$_use = ''; |
760
|
|
|
|
761
|
|
|
// Переберем все что иде после комманды алиаса |
762
|
|
|
for ($j = $i + 1; $j < sizeof($tokens); $j++) { |
|
|
|
|
763
|
|
|
// Получим идентификатор метки и текстовое представление |
764
|
|
|
$id = isset($tokens[$j][0]) ? $tokens[$j][0] : ''; |
765
|
|
|
$text = isset($tokens[$j][1]) ? $tokens[$j][1] : ''; |
766
|
|
|
|
767
|
|
|
//trace('"'.$id.'" - "'.$text.'"'); |
768
|
|
|
|
769
|
|
|
// Если use используется в функции |
770
|
|
|
if ($id == '(') { |
771
|
|
|
$j--; |
772
|
|
|
break; |
773
|
|
|
} |
774
|
|
|
|
775
|
|
|
// Если это закрывающая скобка - прекратим собирание пути к файлу |
776
|
|
|
if ($id == ';') break; |
|
|
|
|
777
|
|
|
|
778
|
|
|
// Все пробелы игнорирую |
779
|
|
|
if ($id == T_WHITESPACE) continue; |
|
|
|
|
780
|
|
|
|
781
|
|
|
// Если у метки есть текстовое представление |
782
|
|
|
if (isset($text)) { |
783
|
|
|
// Если єто константа |
784
|
|
|
if (isset($consts[$text])) $_use .= $consts[$text]; |
|
|
|
|
785
|
|
|
// Если это путь |
786
|
|
|
else $_use .= $text; |
|
|
|
|
787
|
|
|
} |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
// Если это не use в inline функции - добавим алиас в коллекцию |
791
|
|
|
// для данного ns с проверкой на уникальность |
792
|
|
|
if ($id !== '(') { |
793
|
|
|
// If this tait use |
794
|
|
|
if ($classStared) { |
795
|
|
|
// Consider rewriting trait usage fully qualified name |
796
|
|
|
//TODO: Not fully qualified trait name adds slash before |
797
|
|
|
$_use = strpos($_use, '\\') === false |
798
|
|
|
? '\\' . $namespace . '\\' . $_use |
799
|
|
|
: $_use; |
800
|
|
|
|
801
|
|
|
// TODO: Import trait code |
802
|
|
|
if (!trait_exists($_use)) { |
803
|
|
|
throw new \Exception('Trait "' . $_use . '" does not exists in "' . $path . '"'); |
804
|
|
|
} else { |
805
|
|
|
$main_code .= ' use ' . $_use . ';'; |
806
|
|
|
} |
807
|
|
|
|
|
|
|
|
808
|
|
|
} else { |
809
|
|
|
// Преведем все use к одному виду |
810
|
|
|
if ($_use{0} !== '\\') { |
811
|
|
|
$_use = '\\' . $_use; |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
// Add local file uses |
815
|
|
|
$file_uses[] = $_use; |
816
|
|
|
|
817
|
|
|
// TODO: Вывести замечание что бы код везде был одинаковый |
818
|
|
|
if (!in_array($_use, $uses)) { |
819
|
|
|
$uses[] = $_use; |
820
|
|
|
} |
821
|
|
|
} |
822
|
|
|
} else { |
823
|
|
|
$main_code .= ' use '; |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
// Сместим указатель чтения файла |
827
|
|
|
$i = $j; |
828
|
|
|
|
829
|
|
|
break; |
830
|
|
|
|
831
|
|
View Code Duplication |
case T_NAMESPACE: |
|
|
|
|
832
|
|
|
|
833
|
|
|
// Определим временное пространство имен |
834
|
|
|
$_namespace = ''; |
835
|
|
|
|
836
|
|
|
// Переберем все что иде после комманды подключения файла |
837
|
|
|
for ($j = $i + 1; $j < sizeof($tokens); $j++) { |
|
|
|
|
838
|
|
|
// Получим идентификатор метки и текстовое представление |
839
|
|
|
$id = isset($tokens[$j][0]) ? $tokens[$j][0] : ''; |
840
|
|
|
$text = isset($tokens[$j][1]) ? $tokens[$j][1] : ''; |
841
|
|
|
|
842
|
|
|
//trace('"'.$id.'" - "'.$text.'"'); |
843
|
|
|
|
844
|
|
|
// Если это закрывающая скобка - прекратим собирание пути к файлу |
845
|
|
|
if ($id == ')' || $id == ';' || $id == '{') break; |
|
|
|
|
846
|
|
|
|
847
|
|
|
// Все пробелы игнорирую |
848
|
|
|
if ($id == T_WHITESPACE) continue; |
|
|
|
|
849
|
|
|
|
850
|
|
|
// Если у метки есть текстовое представление |
851
|
|
|
if (isset($text)) { |
852
|
|
|
// Если єто константа |
853
|
|
|
if (isset($consts[$text])) $_namespace .= $consts[$text]; |
|
|
|
|
854
|
|
|
// Если это путь |
855
|
|
|
else $_namespace .= $text; |
|
|
|
|
856
|
|
|
} |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
// Если найденный NS отличается от текущего - установим переход к новому NS |
860
|
|
|
if ($namespace !== $_namespace) { |
861
|
|
|
// Сохраним новый как текущий |
862
|
|
|
$namespace = strtolower($_namespace); |
863
|
|
|
|
864
|
|
|
//trace(' #'.$i.' -> Изменили NS с '.$namespace.' на '.$_namespace); |
865
|
|
|
|
866
|
|
|
// Если мы еще не создали данный NS |
867
|
|
|
if (!isset($code[$namespace])) $code[$namespace] = array(); |
|
|
|
|
868
|
|
|
// Создадим уникальную коллекцию алиасов для NS |
869
|
|
|
if (!isset($code[$namespace]['uses'])) $code[$namespace]['uses'] = array(); |
|
|
|
|
870
|
|
|
// Установим ссылку на коллекцию алиасов |
871
|
|
|
$uses = &$code[$namespace]['uses']; |
872
|
|
|
} |
873
|
|
|
|
874
|
|
|
// Сместим указатель чтения файла |
875
|
|
|
$i = $j; |
876
|
|
|
|
877
|
|
|
break; |
878
|
|
|
|
879
|
|
|
// Выделяем код подключаемых файлов |
880
|
|
|
case T_REQUIRE : |
|
|
|
|
881
|
|
|
case T_REQUIRE_ONCE : |
|
|
|
|
882
|
|
|
//case T_INCLUDE : |
883
|
|
View Code Duplication |
case T_INCLUDE_ONCE: { |
|
|
|
|
884
|
|
|
// Получим путь к подключаемому файлу |
885
|
|
|
$file_path = ''; |
886
|
|
|
|
887
|
|
|
// Переберем все что иде после комманды подключения файла |
888
|
|
|
for ($j = $i + 1; $j < sizeof($tokens); $j++) { |
|
|
|
|
889
|
|
|
// Получим идентификатор метки и текстовое представление |
890
|
|
|
$id = isset($tokens[$j][0]) ? $tokens[$j][0] : ''; |
891
|
|
|
$text = isset($tokens[$j][1]) ? $tokens[$j][1] : ''; |
892
|
|
|
|
893
|
|
|
//trace('"'.$id.'" - "'.$text.'"'); |
894
|
|
|
|
895
|
|
|
// Если это закрывающая скобка - прекратим собирание пути к файлу |
896
|
|
|
if ($id == ';') break; |
|
|
|
|
897
|
|
|
|
898
|
|
|
// Все пробелы игнорирую |
899
|
|
|
if ($id == T_WHITESPACE) continue; |
|
|
|
|
900
|
|
|
|
901
|
|
|
// Если у метки есть текстовое представление |
902
|
|
|
if (isset($text)) { |
903
|
|
|
// Если єто константа |
904
|
|
|
if (isset($consts[$text])) $file_path .= $consts[$text]; |
|
|
|
|
905
|
|
|
// Если это путь |
906
|
|
|
else $file_path .= $text; |
|
|
|
|
907
|
|
|
} |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
// Если указан путь к файлу |
911
|
|
|
if (isset($file_path{1})) { |
912
|
|
|
// Уберем ковычки |
913
|
|
|
$file_path = str_replace(array("'", '"'), array('', ''), $file_path); |
914
|
|
|
|
915
|
|
|
// Если это не абсолютный путь - попробуем относительный |
916
|
|
|
if (!file_exists($file_path)) $file_path = pathname($path) . $file_path; |
|
|
|
|
917
|
|
|
|
918
|
|
|
// Если файл найден - получим его содержимое |
919
|
|
|
if (file_exists($file_path)) { |
920
|
|
|
//trace('Углубляемся в файл:'.$file_path.'('.$namespace.')'); |
921
|
|
|
|
922
|
|
|
// Углубимся в рекурсию |
923
|
|
|
$this->compress_php($file_path, $module, $code, $namespace); |
924
|
|
|
|
925
|
|
|
// Измением позицию маркера чтения файла |
926
|
|
|
$i = $j + 1; |
927
|
|
|
} |
928
|
|
|
} else { |
929
|
|
|
$main_code .= $text; |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
} |
933
|
|
|
break; |
934
|
|
|
|
935
|
|
|
case T_INTERFACE: |
936
|
|
|
case T_CLASS: |
937
|
|
|
$classStared = true; |
938
|
|
|
$main_code .= $text; |
939
|
|
|
for ($j = $i + 1; $j < sizeof($tokens); $j++) { |
|
|
|
|
940
|
|
|
// Get id and text of token |
941
|
|
|
$id = isset($tokens[$j][0]) ? $tokens[$j][0] : ''; |
942
|
|
|
$text = isset($tokens[$j][1]) ? $tokens[$j][1] : ''; |
943
|
|
|
|
944
|
|
|
// Ignore all whitespace |
945
|
|
|
if ($id == T_WHITESPACE) continue; |
|
|
|
|
946
|
|
|
|
947
|
|
|
if (isset($text)) { |
948
|
|
|
$className = $text; |
949
|
|
|
break; |
950
|
|
|
} |
951
|
|
|
} |
952
|
|
|
|
953
|
|
|
break; |
954
|
|
|
|
955
|
|
|
case T_CONST: |
956
|
|
|
$main_code .= $text; |
957
|
|
|
$classConst = array(); |
958
|
|
|
$nameFlag = 'name'; |
959
|
|
|
for ($j = $i + 1; $j < sizeof($tokens); $j++) { |
|
|
|
|
960
|
|
|
// Get id and text of token |
961
|
|
|
$id = isset($tokens[$j][0]) ? $tokens[$j][0] : ''; |
962
|
|
|
$text = isset($tokens[$j][1]) ? $tokens[$j][1] : ''; |
963
|
|
|
if ($id == ';') break; |
|
|
|
|
964
|
|
|
|
965
|
|
|
// Ignore all whitespace |
966
|
|
|
if ($id == T_WHITESPACE) continue; |
|
|
|
|
967
|
|
|
|
968
|
|
|
if ($id == '=') { |
969
|
|
|
$nameFlag = 'value'; |
970
|
|
|
continue; |
971
|
|
|
} |
972
|
|
|
|
973
|
|
|
if (isset($text)) { |
974
|
|
|
// Is it defined constant |
975
|
|
|
if (isset($consts[$text])) $classConst[$nameFlag] = $consts[$text]; |
|
|
|
|
976
|
|
|
else $classConst[$nameFlag] = $text; |
|
|
|
|
977
|
|
|
} |
978
|
|
|
} |
979
|
|
|
$classConstList[$classConst['name']] = $classConst['value']; |
980
|
|
|
|
981
|
|
|
break; |
982
|
|
|
|
983
|
|
|
// Собираем основной код программы |
984
|
|
|
default: |
985
|
|
|
$main_code .= $text; |
986
|
|
|
break; |
987
|
|
|
} |
988
|
|
|
} |
989
|
|
|
} |
990
|
|
|
|
991
|
|
|
|
992
|
|
|
// Replace all class shortcut usage with full name |
993
|
|
|
if (count($file_uses)) { |
994
|
|
|
$main_code = $this->removeUSEStatement($main_code, $file_uses); |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
$matches = array(); |
998
|
|
|
if ($className == 'Module') { |
999
|
|
|
$temp = ''; |
|
|
|
|
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
if (preg_match_all('/(?<start>[(=+-\/*%., \n\t])(?<class>[\\\\a-zA-Z_]+)::(?<name>[a-zA-Z_]+)(?<end>[):;=+-\/*%., \n\t])/i', $main_code, $matches)) { |
1003
|
|
|
for ($i = 0; $i < sizeof($matches['name']); $i++) { |
|
|
|
|
1004
|
|
|
$matchClass = $matches['class'][$i]; |
1005
|
|
|
// If this is self - use current file class |
1006
|
|
|
if ($matches['class'][$i] === 'self') { |
1007
|
|
|
$constantName = $namespace . '\\' . $className; |
1008
|
|
|
} elseif ($matches['class'][$i] == $className) { |
1009
|
|
|
// If this is current class add namespace |
1010
|
|
|
$constantName = $namespace . '\\' . $className; |
1011
|
|
|
} elseif ($matches['class'][$i] === 'parent') { |
1012
|
|
|
continue; |
1013
|
|
|
} elseif ($matches['class'][$i] === 'static') { |
1014
|
|
|
continue; |
1015
|
|
|
} else { |
1016
|
|
|
$constantName = $matches['class'][$i]; |
1017
|
|
|
} |
1018
|
|
|
|
1019
|
|
|
// If constant has no namespace - use current |
1020
|
|
|
if (strpos($constantName, '\\') === false) { |
1021
|
|
|
$constantName = $namespace . '\\' . $constantName; |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
// Add constant name |
1025
|
|
|
$constantName .= '::' . $matches['name'][$i]; |
1026
|
|
|
|
1027
|
|
|
$replaceName = $matches['start'][$i] . $matchClass . '::' . $matches['name'][$i] . $matches['end'][$i]; |
1028
|
|
|
|
1029
|
|
|
// Check if we have this constant defined |
1030
|
|
|
if (defined($constantName)) { |
1031
|
|
|
// Get constant value |
1032
|
|
|
$value = constant($constantName); |
1033
|
|
|
// Fix slashes, add quotes for string |
1034
|
|
|
$value = is_string($value) ? str_replace('\\', '\\\\\\\\', "'" . $value . "'") : $value; |
1035
|
|
|
$replacer = str_replace('\\', '\\\\', $replaceName); |
1036
|
|
|
$replacer = str_replace(array(')','('), array('\)', '\('), $replacer); |
1037
|
|
|
// Replace constant call in the code |
1038
|
|
|
$main_code = preg_replace( |
1039
|
|
|
'/' . $replacer . '/i', //([;=+-\/*%., ]) |
1040
|
|
|
$matches['start'][$i] . $value . $matches['end'][$i], |
1041
|
|
|
$main_code |
1042
|
|
|
); |
1043
|
|
|
} |
1044
|
|
|
} |
1045
|
|
|
} |
1046
|
|
|
// Запишем в коллекцию кода полученный код |
1047
|
|
|
$code[$namespace][$path] = $main_code; |
1048
|
|
|
|
1049
|
|
|
return $main_code; |
1050
|
|
|
} |
1051
|
|
|
|
1052
|
|
|
/** |
1053
|
|
|
* Transform class name with namespace to PHP 5.2 format |
1054
|
|
|
* @param $source |
1055
|
|
|
* @param $className |
1056
|
|
|
* @param $php |
1057
|
|
|
* @param $ns |
1058
|
|
|
* |
1059
|
|
|
* @return mixed |
1060
|
|
|
*/ |
1061
|
|
|
private function transformClassName($source, $className, $php, $ns) |
1062
|
|
|
{ |
1063
|
|
|
// Create copy |
1064
|
|
|
$nClassName = trim($className); |
1065
|
|
|
|
1066
|
|
|
// If this class uses other namespace or in global namespace |
1067
|
|
|
if (strrpos($nClassName, '\\') > 0) { |
1068
|
|
|
// If this is full class name |
1069
|
|
|
if ($nClassName{0} == '\\') { |
1070
|
|
|
// Remove global name space character from beginning |
1071
|
|
|
$nClassName = substr($nClassName, 1); |
1072
|
|
|
} |
1073
|
|
|
|
1074
|
|
|
// Transform namespace |
1075
|
|
|
$nClassName = str_replace('\\', '_', $nClassName); |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
} else if ($nClassName{0} == '\\') { // This is global namespace class |
1078
|
|
|
// Remove first character "\" |
1079
|
|
|
$nClassName = substr($nClassName, 1); |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
} else { // No name space in class name |
1082
|
|
|
// Create old-styled namespace format |
1083
|
|
|
$nClassName = str_replace('\\', '_', $ns) . '_' . $nClassName; |
1084
|
|
|
} |
1085
|
|
|
|
1086
|
|
|
// Replace class name in source |
1087
|
|
|
$replace = str_replace($className, $nClassName, $source); |
1088
|
|
|
|
1089
|
|
|
if (strpos($source, 'm(')) { |
1090
|
|
|
//trace($source, true); |
1091
|
|
|
} |
1092
|
|
|
|
1093
|
|
|
// Replace code |
1094
|
|
|
$php = str_ireplace($source, $replace, $php); |
1095
|
|
|
|
1096
|
|
|
//trace('Changing class name('.$ns.')"'.htmlentities(trim($className)).'" with "'.htmlentities(trim($nClassName)).'"'); |
1097
|
|
|
//trace('Replacing "'.htmlentities(trim($source)).'" with "'.htmlentities(trim($replace)).'"'); |
1098
|
|
|
|
1099
|
|
|
return $php; |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
/** |
1103
|
|
|
* Remove blank lines from code |
1104
|
|
|
* http://stackoverflow.com/questions/709669/how-do-i-remove-blank-lines-from-text-in-php |
1105
|
|
|
* @param string $code Code for removing blank lines |
1106
|
|
|
* @return string Modified code |
1107
|
|
|
*/ |
1108
|
|
|
protected function removeBlankLines($code) |
1109
|
|
|
{ |
1110
|
|
|
// New line is required to split non-blank lines |
1111
|
|
|
return preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $code); |
1112
|
|
|
} |
1113
|
|
|
|
1114
|
|
|
/** Change class name to old format without namespace */ |
1115
|
|
|
private function changeClassName($matches, $php, $ns, $uses = array()) |
1116
|
|
|
{ |
1117
|
|
|
// Iterate all class name usage matches |
1118
|
|
|
for ($i = 0; $i < sizeof($matches[0]); $i++) { |
|
|
|
|
1119
|
|
|
// Get source matching string |
1120
|
|
|
$source = $matches[0][$i]; |
1121
|
|
|
|
1122
|
|
|
// Get found classname |
1123
|
|
|
$className = &$matches['classname'][$i]; |
1124
|
|
|
|
1125
|
|
|
// If class name found or this is variable |
1126
|
|
|
if (!isset($className) || !isset($className{0}) || strpos($className, '$') !== false) { |
1127
|
|
|
continue; |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
// Transform class name |
1131
|
|
|
$php = $this->transformClassName($source, $className, $php, $ns, $uses); |
|
|
|
|
1132
|
|
|
} |
1133
|
|
|
|
1134
|
|
|
return $php; |
1135
|
|
|
} |
1136
|
|
|
|
1137
|
|
|
/** |
1138
|
|
|
* Copy resources |
1139
|
|
|
*/ |
1140
|
|
|
private function copy_path_resources($path_resources, $module_path, $module_output_path) |
|
|
|
|
1141
|
|
|
{ |
1142
|
|
|
$this->log(' -> Copying resources from [##] to [##]', $module_path, $module_output_path); |
1143
|
|
|
|
1144
|
|
|
// Iterate module resources |
1145
|
|
|
foreach ($path_resources as $extension => $resources) { |
1146
|
|
|
foreach ($resources as $resource) { |
1147
|
|
|
// Build relative module resource path |
1148
|
|
|
$relative_path = str_replace($module_path, '', $resource); |
1149
|
|
|
|
1150
|
|
|
$this->resourceManager->compress($resource, $this->output . $module_output_path . $relative_path); |
1151
|
|
|
} |
1152
|
|
|
} |
1153
|
|
|
} |
1154
|
|
|
|
1155
|
|
|
/** |
1156
|
|
|
* Remove all USE statements and replace class shortcuts to full class names |
1157
|
|
|
* |
1158
|
|
|
* @param string $code Code to work with |
1159
|
|
|
* @param array $classes Array of class names to replace |
1160
|
|
|
* |
1161
|
|
|
* @return bool|mixed|string |
1162
|
|
|
*/ |
1163
|
|
|
private function removeUSEStatement($code, array $classes) |
1164
|
|
|
{ |
1165
|
|
|
// Iterate found use statements |
1166
|
|
|
foreach (array_unique($classes) as $full_class) { |
1167
|
|
|
// Ignore trait uses |
1168
|
|
|
if (trait_exists($full_class)) { |
1169
|
|
|
continue; |
1170
|
|
|
} |
1171
|
|
|
|
1172
|
|
|
// Get class shortcut |
1173
|
|
|
$class_name = \samson\core\AutoLoader::getOnlyClass($full_class); |
1174
|
|
|
|
1175
|
|
|
// Check class existance |
1176
|
|
|
if (!class_exists($full_class) && !interface_exists($full_class)) { |
1177
|
|
|
//return e('Found USE statement for undeclared class ##', E_SAMSON_FATAL_ERROR, $full_class); |
1178
|
|
|
continue; |
1179
|
|
|
} |
1180
|
|
|
|
1181
|
|
|
// Replace class static call |
1182
|
|
|
$code = preg_replace('/([^\\\a-z])' . $class_name . '::/i', '$1' . $full_class . '::', $code); |
1183
|
|
|
|
1184
|
|
|
// Replace class implements calls |
1185
|
|
|
$code = preg_replace('/\s+implements(.*\W)' . $class_name . '([^\\\])/i', ' implements $1' . $full_class . '$2 ', $code); |
1186
|
|
|
|
1187
|
|
|
// Handle instanceof operator |
1188
|
|
|
$code = preg_replace('/instanceof\s+' . $class_name . '/i', 'instanceof ' . $full_class . '', $code); |
1189
|
|
|
|
1190
|
|
|
// Replace class extends calls |
1191
|
|
|
$code = preg_replace('/extends\s+' . $class_name . '/i', 'extends ' . $full_class . '', $code); |
1192
|
|
|
|
1193
|
|
|
// Replace multiple class extends calls |
1194
|
|
|
$code = preg_replace('/\s+extends(.*\W),?\s' . $class_name . '([^\\\])/i', ' extends $1' . $full_class . '$2 ', $code); |
1195
|
|
|
|
1196
|
|
|
// Replace class hint calls |
1197
|
|
|
$code = preg_replace('/(\(|\s|\,)\s*' . $class_name . '\s*(&|$)/i', '$1' . $full_class . ' $2', $code); |
1198
|
|
|
|
1199
|
|
|
// Replace class creation call |
1200
|
|
|
$code = preg_replace('/new\s+' . $class_name . '\s*\(/i', 'new ' . $full_class . '(', $code); |
1201
|
|
|
|
1202
|
|
|
// Replace annotations |
1203
|
|
|
$code = preg_replace('/([, (])' . $class_name . '\s\$/i', '$1 $2' . $full_class . ' $', $code); |
1204
|
|
|
} |
1205
|
|
|
|
1206
|
|
|
return $code; |
1207
|
|
|
} |
1208
|
|
|
|
1209
|
|
|
/** |
1210
|
|
|
* Преобразовать коллекцию имен классов в коллекцию |
1211
|
|
|
* [Namespace][ ClassFileName ] |
1212
|
|
|
* |
1213
|
|
|
* @param array $collection Коллекция имен классов |
1214
|
|
|
* @param array $classes Коллекция для возврата результатов |
1215
|
|
|
*/ |
1216
|
|
|
private function classes_to_ns_files($collection, & $classes = array()) |
|
|
|
|
1217
|
|
|
{ |
1218
|
|
|
// Соберем коллекцию загруженных интерфейсов их файлов по пространствам имен |
1219
|
|
|
foreach ($collection as $class) { |
1220
|
|
|
$ac = new \ReflectionClass($class); |
1221
|
|
|
|
1222
|
|
|
$ns = $ac->getNamespaceName(); |
1223
|
|
|
|
1224
|
|
|
if ($ns != '') { |
1225
|
|
|
$ns = strtolower($ns); |
1226
|
|
|
|
1227
|
|
|
if (!isset($classes[$ns])) { |
1228
|
|
|
$classes[$ns] = array(); |
1229
|
|
|
} |
1230
|
|
|
|
1231
|
|
|
$classes[$ns][normalizepath($ac->getFileName())] = ''; |
1232
|
|
|
} |
1233
|
|
|
} |
1234
|
|
|
} |
1235
|
|
|
} |
1236
|
|
|
|
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: