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:
Complex classes like AssetsParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AssetsParser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class AssetsParser |
||
17 | { |
||
18 | |||
19 | protected $files = []; |
||
20 | protected $hash = []; |
||
21 | protected $compiled_files; |
||
22 | protected $type; |
||
23 | protected $path; |
||
24 | protected $domains = []; |
||
25 | private $debug = false; |
||
26 | |||
27 | /** |
||
28 | * Constructor por defecto |
||
29 | * |
||
30 | * @param string $type |
||
31 | */ |
||
32 | 1 | public function __construct($type = 'js') |
|
33 | { |
||
34 | 1 | $this->type = $type; |
|
35 | 1 | $this->path = WEB_DIR . DIRECTORY_SEPARATOR; |
|
36 | 1 | $this->domains = Template::getDomains(true); |
|
37 | 1 | $this->debug = Config::getInstance()->getDebugMode(); |
|
38 | 1 | } |
|
39 | |||
40 | /** |
||
41 | * Método que calcula el path completo a copiar un recurso |
||
42 | * @param string $filename_path |
||
43 | * @param string[] $source |
||
44 | * @return string |
||
45 | */ |
||
46 | protected static function calculateResourcePathname($filename_path, $source) |
||
60 | |||
61 | /** |
||
62 | * Método que añade un nuevo fichero al proceso de generación de los assets |
||
63 | * @param $filename |
||
64 | * @return AssetsParser |
||
65 | * @internal param string $type |
||
66 | * |
||
67 | */ |
||
68 | 1 | public function addFile($filename) |
|
69 | { |
||
70 | 1 | if (file_exists($this->path . $filename) && preg_match('/\.' . $this->type . '$/i', $filename)) { |
|
71 | $this->files[] = $filename; |
||
72 | 1 | } elseif (!empty($this->domains)) { |
|
73 | 1 | foreach ($this->domains as $domain => $paths) { |
|
74 | 1 | $domain_filename = str_replace($domain, $paths["public"], $filename); |
|
75 | 1 | if (file_exists($domain_filename) && preg_match('/\.' . $this->type . '$/i', $domain_filename)) { |
|
76 | 1 | $this->files[] = $domain_filename; |
|
77 | 1 | } |
|
78 | 1 | } |
|
79 | 1 | } |
|
80 | 1 | return $this; |
|
81 | } |
||
82 | |||
83 | /** |
||
84 | * Método que establece el hash con el que compilar los assets |
||
85 | * @param string $hash |
||
86 | * |
||
87 | * @return AssetsParser |
||
88 | */ |
||
89 | 1 | public function setHash($hash) |
|
90 | { |
||
91 | 1 | $cache = Config::getParam('cache.var', ''); |
|
92 | 1 | $this->hash = $hash . (strlen($cache) ? '.' : '') . $cache; |
|
93 | 1 | return $this; |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * Método que procesa los ficheros solicitados en función del modo de ejecución |
||
98 | * @return AssetsParser |
||
99 | * @internal param string $type |
||
100 | * @throws ConfigException |
||
101 | */ |
||
102 | 1 | public function compile() |
|
103 | { |
||
104 | //Unificamos ficheros para que no se retarde mucho el proceso |
||
105 | 1 | $this->files = array_unique($this->files); |
|
106 | 1 | switch ($this->type) { |
|
107 | 1 | default: |
|
108 | 1 | case "js": |
|
109 | 1 | $this->compileJs(); |
|
110 | 1 | break; |
|
111 | 1 | case "css": |
|
112 | 1 | $this->compileCss(); |
|
113 | 1 | break; |
|
114 | 1 | } |
|
115 | |||
116 | 1 | return $this; |
|
117 | } |
||
118 | |||
119 | /** |
||
120 | * Método que compila los ficheros css y los procesa en función del modo de ejecución |
||
121 | * @return AssetsParser |
||
122 | * @throws ConfigException |
||
123 | */ |
||
124 | 1 | protected function compileCss() |
|
125 | { |
||
126 | 1 | $base = $this->path . "css" . DIRECTORY_SEPARATOR; |
|
127 | 1 | if ($this->debug || !file_exists($base . $this->hash . ".css")) { |
|
128 | 1 | $data = ''; |
|
129 | 1 | if (0 < count($this->files)) { |
|
130 | 1 | foreach ($this->files as $file) { |
|
131 | 1 | $data = $this->processCssLine($file, $base, $data); |
|
132 | 1 | } |
|
133 | 1 | } |
|
134 | 1 | $this->storeContents($base . $this->hash . ".css", \CssMin::minify($data)); |
|
135 | 1 | unset($cssMinifier); |
|
136 | 1 | } |
|
137 | 1 | return $this; |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * Método que compila los ficheros javascript en función del modo de ejecución |
||
142 | * @return $this |
||
143 | * @throws ConfigException |
||
144 | */ |
||
145 | 1 | protected function compileJs() |
|
146 | { |
||
147 | 1 | $base = $this->path . "js" . DIRECTORY_SEPARATOR; |
|
148 | 1 | if ($this->debug || !file_exists($base . $this->hash . ".js")) { |
|
149 | 1 | $data = ''; |
|
150 | 1 | if (0 < count($this->files)) { |
|
151 | 1 | foreach ($this->files as $file) { |
|
152 | 1 | $path_parts = explode("/", $file); |
|
153 | 1 | if (file_exists($file)) { |
|
154 | 1 | if ($this->debug) { |
|
155 | $data = $this->putDebugJs($path_parts, $base, $file); |
||
156 | 1 | } elseif (!file_exists($base . $this->hash . ".js")) { |
|
157 | 1 | $data = $this->putProductionJs($base, $file, $data); |
|
158 | 1 | } |
|
159 | 1 | } |
|
160 | 1 | } |
|
161 | 1 | } |
|
162 | try { |
||
163 | 1 | $minifiedJs = Minifier::minify($data); |
|
164 | 1 | } catch (\Exception $e) { |
|
165 | 1 | Logger::log($e->getMessage(), LOG_ERR); |
|
166 | 1 | $minifiedJs = false; |
|
167 | } |
||
168 | 1 | $this->storeContents($base . $this->hash . ".js", (false !== $minifiedJs) ? $minifiedJs : $data); |
|
169 | 1 | } |
|
170 | 1 | return $this; |
|
171 | } |
||
172 | |||
173 | /** |
||
174 | * Método para guardar cualquier contenido y controlar que existe el directorio y se guarda correctamente |
||
175 | * @param string $path |
||
176 | * @param string $content |
||
177 | * @throws ConfigException |
||
178 | */ |
||
179 | 1 | View Code Duplication | private function storeContents($path, $content = "") |
180 | { |
||
181 | 1 | GeneratorHelper::createDir(dirname($path)); |
|
182 | 1 | if ("" !== $content && false === file_put_contents($path, $content)) { |
|
183 | throw new ConfigException(_('No se tienen permisos para escribir en ' . $path)); |
||
184 | } |
||
185 | 1 | } |
|
186 | |||
187 | /** |
||
188 | * Método que imprime el resultado de la generación de los assets |
||
189 | */ |
||
190 | 1 | public function printHtml() |
|
191 | { |
||
192 | 1 | switch ($this->type) { |
|
193 | 1 | default: |
|
194 | 1 | case "js": |
|
195 | 1 | $this->printJs(); |
|
196 | 1 | break; |
|
197 | 1 | case "css": |
|
198 | 1 | $this->printCss(); |
|
199 | 1 | break; |
|
200 | 1 | } |
|
201 | 1 | } |
|
202 | |||
203 | /** |
||
204 | * Método que devuelve el html con la ruta compilada del recurso javascript |
||
205 | */ |
||
206 | 1 | View Code Duplication | protected function printJs() |
207 | { |
||
208 | 1 | if ($this->debug && 0 < count($this->compiled_files)) { |
|
209 | foreach ($this->compiled_files as $file) { |
||
210 | echo "\t\t<script type='text/javascript' src='{$file}'></script>\n"; |
||
211 | } |
||
212 | } else { |
||
213 | 1 | echo "\t\t<script type='text/javascript' src='/js/" . $this->hash . ".js'></script>\n"; |
|
214 | } |
||
215 | 1 | } |
|
216 | |||
217 | /** |
||
218 | * Método que devuelve el html con la ruta compilada del recurso css |
||
219 | */ |
||
220 | 1 | View Code Duplication | protected function printCss() |
221 | { |
||
222 | 1 | if ($this->debug && 0 < count($this->compiled_files)) { |
|
223 | foreach ($this->compiled_files as $file) { |
||
224 | echo "\t\t<link href='{$file}' rel='stylesheet' media='screen, print'>"; |
||
225 | } |
||
226 | } else { |
||
227 | 1 | echo "\t\t<link href='/css/" . $this->hash . ".css' rel='stylesheet'>"; |
|
228 | } |
||
229 | 1 | } |
|
230 | |||
231 | /** |
||
232 | * @param string $source |
||
233 | * @param string $file |
||
234 | */ |
||
235 | 1 | protected function extractCssResources($source, $file) |
|
236 | { |
||
237 | 1 | $source_file = $this->extractSourceFilename($source); |
|
238 | 1 | $orig = realpath(dirname($file) . DIRECTORY_SEPARATOR . $source_file); |
|
239 | 1 | $orig_part = preg_split('/(\/|\\\)public(\/|\\\)/i', $orig); |
|
240 | try { |
||
241 | 1 | if (count($source) > 1 && array_key_exists(1, $orig_part)) { |
|
242 | 1 | $dest = $this->path . $orig_part[1]; |
|
243 | 1 | GeneratorHelper::createDir(dirname($dest)); |
|
244 | 1 | if (!file_exists($dest) || filemtime($orig) > filemtime($dest)) { |
|
245 | 1 | if (@copy($orig, $dest) === FALSE) { |
|
246 | throw new \RuntimeException('Can\' copy ' . $dest . ''); |
||
247 | } |
||
248 | 1 | Logger::log("$orig copiado a $dest", LOG_INFO); |
|
249 | 1 | } |
|
250 | 1 | } |
|
251 | 1 | } catch (\Exception $e) { |
|
252 | Logger::log($e->getMessage(), LOG_ERR); |
||
253 | } |
||
254 | 1 | } |
|
255 | |||
256 | /** |
||
257 | * Método que procesa cada línea de la hoja de estilos para copiar los recursos asociados |
||
258 | * @param string $file |
||
259 | * @param string $base |
||
260 | * @param string $data |
||
261 | * @return string |
||
262 | * @throws ConfigException |
||
263 | */ |
||
264 | 1 | protected function processCssLine($file, $base, $data) |
|
265 | { |
||
266 | 1 | if (file_exists($file)) { |
|
267 | 1 | $path_parts = explode("/", $file); |
|
268 | 1 | $file_path = $this->hash . "_" . $path_parts[count($path_parts) - 1]; |
|
269 | 1 | if (!file_exists($base . $file_path) || filemtime($base . $file_path) < filemtime($file) || $this->debug) { |
|
270 | //Si tenemos modificaciones tenemos que compilar de nuevo todos los ficheros modificados |
||
271 | 1 | if (file_exists($base . $this->hash . ".css") && @unlink($base . $this->hash . ".css") === false) { |
|
272 | throw new ConfigException("Can't unlink file " . $base . $this->hash . ".css"); |
||
273 | } |
||
274 | 1 | $this->loopCssLines($file); |
|
275 | 1 | } |
|
276 | 1 | if ($this->debug) { |
|
277 | $data = file_get_contents($file); |
||
278 | $this->storeContents($base . $file_path, $data); |
||
279 | } else { |
||
280 | 1 | $data .= file_get_contents($file); |
|
281 | } |
||
282 | 1 | $this->compiled_files[] = "/css/" . $file_path; |
|
283 | 1 | } |
|
284 | |||
285 | 1 | return $data; |
|
286 | } |
||
287 | |||
288 | /** |
||
289 | * @param $path_parts |
||
290 | * @param string $base |
||
291 | * @param $file |
||
292 | * @return string |
||
293 | * @throws ConfigException |
||
294 | */ |
||
295 | protected function putDebugJs($path_parts, $base, $file) |
||
306 | |||
307 | /** |
||
308 | * @param string $base |
||
309 | * @param $file |
||
310 | * @param string $data |
||
311 | * |
||
312 | * @return string |
||
313 | * @throws ConfigException |
||
314 | */ |
||
315 | 1 | protected function putProductionJs($base, $file, $data) |
|
316 | { |
||
317 | 1 | $js = file_get_contents($file); |
|
318 | try { |
||
319 | 1 | $data .= ";\n" . $js; |
|
320 | 1 | } catch (\Exception $e) { |
|
321 | throw new ConfigException($e->getMessage()); |
||
322 | } |
||
323 | 1 | return $data; |
|
324 | } |
||
325 | |||
326 | /** |
||
327 | * Servicio que busca el path para un dominio dado |
||
328 | * @param $string |
||
329 | * @param string $file_path |
||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | public static function findDomainPath($string, $file_path) |
||
349 | |||
350 | /** |
||
351 | * Método que calcula el path de un recurso web |
||
352 | * @param string $string |
||
353 | * @param string $name |
||
354 | * @param boolean $return |
||
355 | * @param string $filename_path |
||
356 | * |
||
357 | * @return string[] |
||
358 | */ |
||
359 | public static function calculateAssetPath($string, $name, $return, $filename_path) |
||
360 | { |
||
361 | $ppath = explode("/", $string); |
||
362 | $original_filename = $ppath[count($ppath) - 1]; |
||
420 | |||
421 | /** |
||
422 | * Método que extrae el recurso css de una línea de estilos css |
||
423 | * @param $handle |
||
424 | * @param string $filename_path |
||
425 | * @throws ConfigException |
||
426 | */ |
||
427 | public static function extractCssLineResource($handle, $filename_path) |
||
443 | |||
444 | /** |
||
445 | * Método que extrae el nombre del fichero de un recurso |
||
446 | * @param string $source |
||
447 | * @return string |
||
448 | */ |
||
449 | 1 | protected function extractSourceFilename($source) |
|
463 | |||
464 | /** |
||
465 | * @param string $file |
||
466 | */ |
||
467 | 1 | protected function loopCssLines($file) |
|
483 | } |
||
484 |
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.