1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by Vitaly Iegorov <[email protected]> |
4
|
|
|
* on 19.09.2014 at 17:26 |
5
|
|
|
*/ |
6
|
|
|
namespace samsonphp\compressor; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Compressor form SamsonPHP Event system |
10
|
|
|
* @author Vitaly Egorov <[email protected]> |
11
|
|
|
* @copyright 2014 SamsonOS |
12
|
|
|
*/ |
13
|
|
|
class EventCompressor |
14
|
|
|
{ |
15
|
|
|
/** @var array Collection of event subscriptions and handlers */ |
16
|
|
|
public $subscriptions = array(); |
17
|
|
|
|
18
|
|
|
/** @var array Collection of event fires */ |
19
|
|
|
public $fires = array(); |
20
|
|
|
|
21
|
|
|
protected function & parseSubscription(array & $matches) |
22
|
|
|
{ |
23
|
|
|
// Resulting collection of arrays |
24
|
|
|
$events = array(); |
25
|
|
|
|
26
|
|
|
// Iterate all matches based on event identifiers |
27
|
|
|
for ($i=0,$l = sizeof($matches['id']); $i < $l; $i++) { |
28
|
|
|
// Pointer to events collection |
29
|
|
|
$event = & $events[trim($matches['id'][$i])]; |
30
|
|
|
// Create collection for this event identifier |
31
|
|
|
$event = isset($event) ? $event : array(); |
32
|
|
|
// Get handler code |
33
|
|
|
$handler = rtrim(trim($matches['handler'][$i]), ')'); |
34
|
|
|
// Add ')' if this is an array callback |
35
|
|
|
$handler = stripos($handler, 'array') !== false ? $handler.')' : $handler; |
36
|
|
|
|
37
|
|
|
// Create subscription metadata |
38
|
|
|
$metadata = array( |
39
|
|
|
'source' => $matches[0][$i] |
40
|
|
|
); |
41
|
|
|
|
42
|
|
|
// If this is object method callback - parse it |
43
|
|
|
$args = array(); |
44
|
|
|
if (preg_match('/\s*array\s*\((?<object>[^,]+)\s*,\s*(\'|\")(?<method>[^\'\"]+)/ui', $handler, $args)) { |
45
|
|
|
// If this is static |
46
|
|
|
$metadata['object'] = $args['object']; |
47
|
|
|
$metadata['method'] = $args['method']; |
48
|
|
|
} else { //global function |
49
|
|
|
$metadata['method'] = str_replace(array('"',"'"), '', $handler); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
// Add event callback |
53
|
|
|
$event[] = $metadata; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
return $events; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Find and gather all static event subscription calls |
61
|
|
|
* @param string $code PHP code for searching |
62
|
|
|
* |
63
|
|
|
* @return array Collection event subscription collection |
64
|
|
|
*/ |
65
|
|
View Code Duplication |
public function findAllStaticSubscriptions($code) |
|
|
|
|
66
|
|
|
{ |
67
|
|
|
// Found collection |
68
|
|
|
$matches = array(); |
69
|
|
|
|
70
|
|
|
// Matching pattern |
71
|
|
|
$pattern = '/(\\\\samsonphp\\\\event\\\\|samson_core_|\\\samson\\\\core\\\\)*Event::subscribe\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*,\s*(?<handler>[^;-]+)/ui'; |
72
|
|
|
|
73
|
|
|
// Perform text search |
74
|
|
|
if (preg_match_all($pattern, $code, $matches)) { |
75
|
|
|
// Additional handling |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
// Call generic subscription parser |
79
|
|
|
return $this->parseSubscription($matches); |
|
|
|
|
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Find all event fire calls in code |
84
|
|
|
* |
85
|
|
|
* @param $code |
86
|
|
|
* |
87
|
|
|
* @return array |
88
|
|
|
*/ |
89
|
|
|
public function findAllFires($code) |
90
|
|
|
{ |
91
|
|
|
// Resulting collection of arrays |
92
|
|
|
$events = array(); |
93
|
|
|
|
94
|
|
|
// Found collection |
95
|
|
|
$matches = array(); |
96
|
|
|
|
97
|
|
|
// Matching pattern |
98
|
|
|
$pattern = '/(\\\\samsonphp\\\\event\\\\|samson_core_|\\\samson\\\core\\\)?Event::(fire|signal)\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*(,\s*(?<params>[^;]+)|\s*\))?/ui'; |
99
|
|
|
|
100
|
|
|
// TODO: Move to token_get_all(); |
101
|
|
|
|
102
|
|
|
// Perform text search |
103
|
|
|
if (preg_match_all($pattern, $code, $matches)) { |
104
|
|
|
// Iterate all matches based on event identifiers |
105
|
|
|
for ($i=0,$l = sizeof($matches['id']); $i < $l; $i++) { |
106
|
|
|
// Get handler code |
107
|
|
|
$params = trim($matches['params'][$i]); |
108
|
|
|
|
109
|
|
|
// If this is signal fire - remove last 'true' parameter |
110
|
|
|
$match = array(); |
111
|
|
|
if (preg_match('/\),\s*true\s*\)/', $params, $match)) { |
112
|
|
|
$params = str_replace($match[0], '', $params); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Remove spaces and new lines |
116
|
|
|
$params = preg_replace('/[ \t\n\r]+/', '', $params); |
117
|
|
|
|
118
|
|
|
// Parse all fire parameters |
119
|
|
|
$args = array(); |
120
|
|
|
if (preg_match('/\s*array\s*\((?<parameters>[^;]+)/ui', $params, $args)) { |
121
|
|
|
// Remove reference symbol as we do not need it |
122
|
|
|
$params = array(); |
123
|
|
|
foreach (explode(',', $args['parameters']) as $parameter) { |
124
|
|
|
$params[] = str_replace(array('))', '&'), '', $parameter); |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// Add event callback |
129
|
|
|
$events[trim($matches['id'][$i])] = array( |
130
|
|
|
'params' => $params, |
131
|
|
|
'source' => $matches[0][$i] |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
return $events; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Find and gather all dynamic event subscription calls |
141
|
|
|
* @param string $code PHP code for searching |
142
|
|
|
* |
143
|
|
|
* @return array Collection event subscription collection |
144
|
|
|
*/ |
145
|
|
View Code Duplication |
public function findAllDynamicSubscriptions($code) |
|
|
|
|
146
|
|
|
{ |
147
|
|
|
// Found collection |
148
|
|
|
$matches = array(); |
149
|
|
|
|
150
|
|
|
// Matching pattern |
151
|
|
|
$pattern = '/\s*->\s*subscribe\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*,\s*(?<handler>[^;-]+)/ui'; |
152
|
|
|
|
153
|
|
|
// Perform text search |
154
|
|
|
if (preg_match_all($pattern, $code, $matches)) { |
155
|
|
|
// Additional handling |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
// Call generic subscription parser |
159
|
|
|
return $this->parseSubscription($matches); |
|
|
|
|
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Analyze code and gather event system calls |
164
|
|
|
* @param string $input PHP code for analyzing |
165
|
|
|
*/ |
166
|
|
|
public function collect($input) |
167
|
|
|
{ |
168
|
|
|
// Gather all subscriptions |
169
|
|
|
$this->subscriptions = array_merge_recursive( |
170
|
|
|
$this->subscriptions, |
171
|
|
|
$this->findAllDynamicSubscriptions($input), |
172
|
|
|
$this->findAllStaticSubscriptions($input) |
173
|
|
|
); |
174
|
|
|
|
175
|
|
|
// Gather events fires |
176
|
|
|
$this->fires = array_merge_recursive( |
177
|
|
|
$this->fires, |
178
|
|
|
$this->findAllFires($input) |
179
|
|
|
); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Remove all event subscription calls |
184
|
|
|
* @param array $subscriptions collection of subscription groups |
185
|
|
|
* @param string $code Code for removing subscriptions |
186
|
|
|
* @return string Modified code with removed subscriptions(if were present) |
187
|
|
|
*/ |
188
|
|
|
public function removeSubscriptionCalls($subscriptions, $code) |
189
|
|
|
{ |
190
|
|
|
// Iterate all subscription groups |
191
|
|
|
foreach ($subscriptions as $eventID => $subscriptionGroup) { |
192
|
|
|
// Iterate all subscriptions in group |
193
|
|
|
foreach ($subscriptionGroup as $subscription) { |
194
|
|
|
// Remove all event subscription in code |
195
|
|
|
$code = str_replace($subscription['source'], '', $code); |
196
|
|
|
|
197
|
|
|
$this->log('Removing event [##] subscription [##]', $eventID, $subscription['source']); |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
return $code; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
public function transform($input, & $output = '') |
205
|
|
|
{ |
206
|
|
|
// Get all defined handlers |
207
|
|
|
$handlers = \samsonphp\event\Event::listeners(); |
208
|
|
|
|
209
|
|
|
// Iterate all event fire calls |
210
|
|
|
foreach ($this->fires as $id => $data) { |
|
|
|
|
211
|
|
|
|
212
|
|
|
// Collection of actual event handler call for replacement |
213
|
|
|
$code = array(); |
214
|
|
|
|
215
|
|
|
// Set pointer to event subscriptions collection |
216
|
|
|
if (isset($this->subscriptions[$id])) { |
217
|
|
|
// Iterate event subscriptions |
218
|
|
|
foreach ($this->subscriptions[$id] as &$event) { |
219
|
|
|
$this->log('Analyzing event subscription[##]', $id); |
220
|
|
|
// If subscriber callback is object method |
221
|
|
|
if (isset($event['object'])) { |
222
|
|
|
$eventHandlers = & $handlers[$id]; |
223
|
|
|
if (isset($eventHandlers)) { |
224
|
|
|
// Iterate all handlers |
225
|
|
|
foreach ($eventHandlers as $handler) { |
|
|
|
|
226
|
|
|
|
227
|
|
|
//trace($handler); |
228
|
|
|
$call = ''; |
229
|
|
|
|
230
|
|
|
// Get pointer to object |
231
|
|
|
if (is_scalar($handler[0][0])) { |
232
|
|
|
$object = $handler[0][0]; |
233
|
|
|
} else { |
234
|
|
|
$object = & $handler[0][0]; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
// TODO: Not existing dynamic handlers what was excluded from compressed code |
238
|
|
|
|
239
|
|
|
if(is_object($object) && $object instanceof \samsonframework\core\ViewInterface && $object instanceof \samsonframework\core\CompressInterface) { |
|
|
|
|
240
|
|
|
// Build object method call |
241
|
|
|
$call = 'm("' . $object->id() . '")->' . $event['method'] . '('; |
242
|
|
|
$this->log(' - Replacing event fire[##] with object function call [##]', $id, $call); |
243
|
|
|
} elseif (strpos($event['object'], '(') !== false) { // Function |
244
|
|
|
// Build object method call |
245
|
|
|
$call = $event['object'].'->' . $event['method'] . '('; |
246
|
|
|
} elseif (is_string($object) && class_exists($object, false)) { // Static class |
247
|
|
|
//trace($event['object'].'-'.$object); |
248
|
|
|
|
249
|
|
|
// Build object method call |
250
|
|
|
$call = $event['object'].'::' . $event['method'] . '('; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
// TODO: Define what to do with other classes, only functions supported |
254
|
|
|
// If we have found correct object |
255
|
|
|
if (isset($call{0})) { |
256
|
|
|
// Event fire passes parameters |
257
|
|
|
if (is_array($data['params'])) { |
258
|
|
|
$call .= implode(', ', $data['params']); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
// Gather object calls |
262
|
|
|
$code[] = $call .');'; |
263
|
|
|
} else { |
264
|
|
|
$this->log(' - Cannot replace event fire[##] with [##] - [##]', $id, $event['object'], $event['method']); |
265
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
} else { // Global function |
270
|
|
|
if (strpos($event['method'], '$') === false) { |
271
|
|
|
$call = $event['method'] . '(' . implode(', ', $data['params']) . ');'; |
272
|
|
|
$code[] = $call; |
273
|
|
|
$this->log(' - Replacing event fire[##] with function call [##]', $id, $call); |
274
|
|
|
} else { |
275
|
|
|
$this->log('Cannot replace event fire method with [##] - variables not supported', $event['method']); |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Remove duplicates |
281
|
|
|
$code = array_unique($code); |
282
|
|
|
|
283
|
|
|
// Replace Event::fire call with actual handlers |
284
|
|
|
$input = str_replace($data['source'], implode("\n", $code), $input); |
285
|
|
|
|
286
|
|
|
// Logging changes in code |
287
|
|
|
foreach ($code as $replace) { |
288
|
|
|
$this->log('Replacing [##] with [##]', $data['source'], $replace); |
289
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
} else { // There is no subscriptions to this event fire |
292
|
|
|
// Remove Event::fire call without subscriptions |
293
|
|
|
$input = str_replace($data['source'], '', $input); |
294
|
|
|
|
295
|
|
|
$this->log('Removing event firing [##] as it has no subscriptions', $data['source']); |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
// Remove all subscriptions from code |
300
|
|
|
$input = $this->removeSubscriptionCalls($this->subscriptions, $input); |
301
|
|
|
|
302
|
|
|
// Copy output |
303
|
|
|
$output = $input; |
304
|
|
|
|
305
|
|
|
return true; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** Generic log function for further modification */ |
309
|
|
|
protected function log($message) |
310
|
|
|
{ |
311
|
|
|
// Get passed vars |
312
|
|
|
$vars = func_get_args(); |
313
|
|
|
// Remove first message var |
314
|
|
|
array_shift($vars); |
315
|
|
|
|
316
|
|
|
// Render debug message |
317
|
|
|
return trace(debug_parse_markers($message, $vars)); |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.