EventCompressor::collect()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 1
eloc 8
nc 1
nop 1
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $matches can also be of type null; however, samsonphp\compressor\Eve...or::parseSubscription() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $matches can also be of type null; however, samsonphp\compressor\Eve...or::parseSubscription() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
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) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
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) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
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
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
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
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
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