Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
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) |
||
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) |
|
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) |
|
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) |
||
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.