| Total Complexity | 51 |
| Total Lines | 272 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Merger 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.
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 Merger, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 22 | class Merger implements MergerInterface |
||
| 23 | { |
||
| 24 | /** |
||
| 25 | * @var RouterInterface |
||
| 26 | */ |
||
| 27 | private $router; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * @var ZikulaHttpKernelInterface |
||
| 31 | */ |
||
| 32 | private $kernel; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @var string |
||
| 36 | */ |
||
| 37 | private $rootDir; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @var integer |
||
| 41 | */ |
||
| 42 | private $lifetime; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @var boolean |
||
| 46 | */ |
||
| 47 | private $minify; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * @var boolean |
||
| 51 | */ |
||
| 52 | private $compress; |
||
| 53 | |||
| 54 | public function __construct( |
||
| 55 | RouterInterface $router, |
||
| 56 | ZikulaHttpKernelInterface $kernel, |
||
| 57 | string $lifetime = '1 day', |
||
| 58 | bool $minify = false, |
||
| 59 | bool $compress = false |
||
| 60 | ) { |
||
| 61 | $this->router = $router; |
||
| 62 | $this->kernel = $kernel; |
||
| 63 | $projectDir = realpath($kernel->getProjectDir() . '/'); |
||
|
|
|||
| 64 | $this->rootDir = str_replace($router->getContext()->getBaseUrl(), '', $projectDir); |
||
| 65 | $this->lifetime = abs((new DateTime($lifetime))->getTimestamp() - (new DateTime())->getTimestamp()); |
||
| 66 | $this->minify = $minify; |
||
| 67 | $this->compress = $compress; |
||
| 68 | } |
||
| 69 | |||
| 70 | public function merge(array $assets, $type = 'js'): array |
||
| 71 | { |
||
| 72 | if (!in_array($type, ['js', 'css'])) { |
||
| 73 | return []; |
||
| 74 | } |
||
| 75 | |||
| 76 | $preCachedFiles = []; |
||
| 77 | $cachedFiles = []; |
||
| 78 | $outputFiles = []; |
||
| 79 | // skip remote files from combining |
||
| 80 | foreach ($assets as $asset => $weight) { |
||
| 81 | $path = realpath($this->rootDir . $asset); |
||
| 82 | if (false !== $path && is_file($path)) { |
||
| 83 | $cachedFiles[] = $path; |
||
| 84 | } elseif ($weight < 0) { |
||
| 85 | $preCachedFiles[$asset] = $weight; |
||
| 86 | } else { |
||
| 87 | $outputFiles[$asset] = $weight; |
||
| 88 | } |
||
| 89 | } |
||
| 90 | $cacheService = new FilesystemAdapter( |
||
| 91 | 'combined_assets', |
||
| 92 | $this->lifetime, |
||
| 93 | $this->kernel->getCacheDir() . '/assets/' . $type); |
||
| 94 | $key = md5(serialize($assets)) . (int)$this->minify . (int)$this->compress . $this->lifetime . '.combined.' . $type; |
||
| 95 | $data = $cacheService->get($key, function() use ($cacheService, $cachedFiles, $type) { |
||
| 96 | $data = []; |
||
| 97 | foreach ($cachedFiles as $k => $file) { |
||
| 98 | $this->readFile($data, $file, $type); |
||
| 99 | // avoid exposure of absolute server path |
||
| 100 | $pathParts = explode($this->rootDir, $file); |
||
| 101 | $cachedFiles[$k] = end($pathParts); |
||
| 102 | } |
||
| 103 | $now = new DateTime(); |
||
| 104 | array_unshift($data, sprintf("/* --- Combined file written: %s */\n\n", $now->format('c'))); |
||
| 105 | array_unshift($data, sprintf("/* --- Combined files:\n%s\n*/\n\n", implode("\n", $cachedFiles))); |
||
| 106 | $data = implode('', $data); |
||
| 107 | if ('css' === $type && $this->minify) { |
||
| 108 | $data = $this->minify($data); |
||
| 109 | } |
||
| 110 | |||
| 111 | return $data; |
||
| 112 | }); |
||
| 113 | |||
| 114 | $route = $this->router->generate('zikulathememodule_combinedasset_asset', ['type' => $type, 'key' => $key]); |
||
| 115 | $outputFiles[$route] = AssetBag::WEIGHT_DEFAULT; |
||
| 116 | |||
| 117 | $outputFiles = array_merge($preCachedFiles, $outputFiles); |
||
| 118 | |||
| 119 | return $outputFiles; |
||
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Read a file and add its contents to the $contents array. |
||
| 124 | * This function includes the content of all "@import" statements (recursive). |
||
| 125 | */ |
||
| 126 | private function readFile(array &$contents, string $file, string $ext): void |
||
| 127 | { |
||
| 128 | if (!file_exists($file)) { |
||
| 129 | return; |
||
| 130 | } |
||
| 131 | $source = fopen($file, 'rb'); |
||
| 132 | if (!$source) { |
||
| 133 | return; |
||
| 134 | } |
||
| 135 | |||
| 136 | // avoid exposure of absolute server path |
||
| 137 | $pathParts = explode($this->rootDir, $file); |
||
| 138 | $relativePath = end($pathParts); |
||
| 139 | $contents[] = "/* --- Source file: {$relativePath} */\n\n"; |
||
| 140 | $inMultilineComment = false; |
||
| 141 | $importsAllowd = true; |
||
| 142 | $wasCommentHack = false; |
||
| 143 | while (!feof($source)) { |
||
| 144 | if ('css' === $ext) { |
||
| 145 | $line = fgets($source, 4096); |
||
| 146 | $lineParse = false !== $line ? trim($line) : ''; |
||
| 147 | $lineParse_length = mb_strlen($lineParse, 'UTF-8'); |
||
| 148 | $newLine = ''; |
||
| 149 | // parse line char by char |
||
| 150 | for ($i = 0; $i < $lineParse_length; $i++) { |
||
| 151 | $char = $lineParse[$i]; |
||
| 152 | $nextchar = $i < ($lineParse_length - 1) ? $lineParse[$i + 1] : ''; |
||
| 153 | if (!$inMultilineComment && '/' === $char && '*' === $nextchar) { |
||
| 154 | // a multiline comment starts here |
||
| 155 | $inMultilineComment = true; |
||
| 156 | $wasCommentHack = false; |
||
| 157 | $newLine .= $char . $nextchar; |
||
| 158 | $i++; |
||
| 159 | } elseif ($inMultilineComment && '*' === $char && '/' === $nextchar) { |
||
| 160 | // a multiline comment stops here |
||
| 161 | $inMultilineComment = false; |
||
| 162 | $newLine .= $char . $nextchar; |
||
| 163 | if ('/*\*//*/' === mb_substr($lineParse, $i - 3, 8)) { |
||
| 164 | $wasCommentHack = true; |
||
| 165 | $i += 3; // move to end of hack process hack as it where |
||
| 166 | $newLine .= '/*/'; // fix hack comment because we lost some chars with $i += 3 |
||
| 167 | } |
||
| 168 | $i++; |
||
| 169 | } elseif ($importsAllowd && '@' === $char && '@import' === mb_substr($lineParse, $i, 7)) { |
||
| 170 | // an @import starts here |
||
| 171 | $lineParseRest = trim(mb_substr($lineParse, $i + 7)); |
||
| 172 | if (0 === mb_stripos($lineParseRest, 'url')) { |
||
| 173 | // the @import uses url to specify the path |
||
| 174 | $posEnd = mb_strpos($lineParse, ';', $i); |
||
| 175 | $charsEnd = mb_substr($lineParse, $posEnd - 1, 2); |
||
| 176 | if (');' === $charsEnd) { |
||
| 177 | // used url() without media |
||
| 178 | $start = mb_strpos($lineParseRest, '(') + 1; |
||
| 179 | $end = mb_strpos($lineParseRest, ')'); |
||
| 180 | $url = mb_substr($lineParseRest, $start, $end - $start); |
||
| 181 | if (0 === mb_strpos($url, '"') | 0 === mb_strpos($url, "'")) { |
||
| 182 | $url = mb_substr($url, 1, -1); |
||
| 183 | } |
||
| 184 | // fix url |
||
| 185 | $url = dirname($file) . '/' . $url; |
||
| 186 | if (!$wasCommentHack) { |
||
| 187 | // clear buffer |
||
| 188 | $contents[] = $newLine; |
||
| 189 | $newLine = ''; |
||
| 190 | // process include |
||
| 191 | $this->readFile($contents, $url, $ext); |
||
| 192 | } else { |
||
| 193 | $newLine .= '@import url("' . $url . '");'; |
||
| 194 | } |
||
| 195 | // skip @import statement |
||
| 196 | $i += $posEnd - $i; |
||
| 197 | } else { |
||
| 198 | // @import contains media type so we can't include its contents. |
||
| 199 | // We need to fix the url instead. |
||
| 200 | $start = mb_strpos($lineParseRest, '(') + 1; |
||
| 201 | $end = mb_strpos($lineParseRest, ')'); |
||
| 202 | $url = mb_substr($lineParseRest, $start, $end - $start); |
||
| 203 | if (0 === mb_strpos($url, '"') | 0 === mb_strpos($url, "'")) { |
||
| 204 | $url = mb_substr($url, 1, -1); |
||
| 205 | } |
||
| 206 | // fix url |
||
| 207 | $url = dirname($file) . '/' . $url; |
||
| 208 | // readd @import with fixed url |
||
| 209 | $newLine .= '@import url("' . $url . '")' . mb_substr($lineParseRest, $end + 1, mb_strpos($lineParseRest, ';') - $end - 1) . ';'; |
||
| 210 | // skip @import statement |
||
| 211 | $i += $posEnd - $i; |
||
| 212 | } |
||
| 213 | } elseif (0 === mb_strpos($lineParseRest, '"') || 0 === mb_strpos($lineParseRest, '\'')) { |
||
| 214 | // the @import uses an normal string to specify the path |
||
| 215 | $posEnd = mb_strpos($lineParseRest, ';'); |
||
| 216 | $url = mb_substr($lineParseRest, 1, $posEnd - 2); |
||
| 217 | $posEnd = mb_strpos($lineParse, ';', $i); |
||
| 218 | // fix url |
||
| 219 | $url = dirname($file) . '/' . $url; |
||
| 220 | if (!$wasCommentHack) { |
||
| 221 | // clear buffer |
||
| 222 | $contents[] = $newLine; |
||
| 223 | $newLine = ''; |
||
| 224 | // process include |
||
| 225 | self::readFile($contents, $url, $ext); |
||
| 226 | } else { |
||
| 227 | $newLine .= '@import url("' . $url . '");'; |
||
| 228 | } |
||
| 229 | // skip @import statement |
||
| 230 | $i += $posEnd - $i; |
||
| 231 | } |
||
| 232 | } elseif (!$inMultilineComment && ' ' !== $char && "\n" !== $char && "\r\n" !== $char && "\r" !== $char) { |
||
| 233 | // css rule found -> stop processing of @import statements |
||
| 234 | $importsAllowd = false; |
||
| 235 | $newLine .= $char; |
||
| 236 | } else { |
||
| 237 | $newLine .= $char; |
||
| 238 | } |
||
| 239 | } |
||
| 240 | // fix other paths after @import processing |
||
| 241 | if (!$importsAllowd) { |
||
| 242 | $relativePath = str_replace(realpath($this->rootDir), '', $file); |
||
| 243 | $newLine = $this->cssFixPath($newLine, explode('/', dirname($relativePath))); |
||
| 244 | } |
||
| 245 | $contents[] = $newLine; |
||
| 246 | } else { |
||
| 247 | $contents[] = fgets($source, 4096); |
||
| 248 | } |
||
| 249 | } |
||
| 250 | fclose($source); |
||
| 251 | if ('js' === $ext) { |
||
| 252 | $contents[] = "\n;\n"; |
||
| 253 | } else { |
||
| 254 | $contents[] = "\n\n"; |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | /** |
||
| 259 | * Fix paths in CSS files. |
||
| 260 | */ |
||
| 261 | private function cssFixPath(string $line, array $filePathSegments = []): string |
||
| 279 | } |
||
| 280 | |||
| 281 | /** |
||
| 282 | * Remove comments, whitespace and spaces from css files. |
||
| 283 | */ |
||
| 284 | private function minify(string $input): string |
||
| 294 | } |
||
| 295 | } |
||
| 296 |