Complex classes like CssEmbed 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 CssEmbed, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class CssEmbed |
||
16 | { |
||
17 | |||
18 | const SEARCH_PATTERN = "%url\\(['\" ]*((?!data:|//)[^'\"#\?: ]+)['\" ]*\\)%U"; |
||
19 | const URI_PATTERN = "url(data:%s;base64,%s)"; |
||
20 | |||
21 | const HTTP_SEARCH_PATTERN = "%url\\(['\" ]*((?!data:)[^'\" ]+)['\" ]*\\)%U"; |
||
22 | const HTTP_ENABLED = 1; |
||
23 | const HTTP_DEFAULT_HTTPS = 2; |
||
24 | const HTTP_URL_ON_ERROR = 4; |
||
25 | const HTTP_EMBED_FONTS = 8; |
||
26 | const HTTP_EMBED_SVG = 16; |
||
27 | const HTTP_EMBED_SCHEME = 32; |
||
28 | const HTTP_EMBED_URL_ONLY = 64; |
||
29 | |||
30 | protected $root_dir; |
||
31 | |||
32 | /** @var integer the http flags */ |
||
33 | protected $http_flags = 0; |
||
34 | |||
35 | /** |
||
36 | * @param $root_dir |
||
37 | */ |
||
38 | public function setRootDir($root_dir) |
||
42 | |||
43 | /** |
||
44 | * Allow assets referenced over HTTP to be embedded, or assets in a css |
||
45 | * file loaded over HTTP. Flags: |
||
46 | * |
||
47 | * - CssEmbed::HTTP_ENABLED: enable embedding over http; |
||
48 | * - CssEmbed::HTTP_DEFAULT_HTTPS: for URLs with no scheme, use https to |
||
49 | * instead of http |
||
50 | * - CssEmbed::HTTP_URL_ON_ERROR: if there is an error fetching a remote |
||
51 | * asset, embed the URL instead of throwing an exception |
||
52 | * - CssEmbed::HTTP_EMBED_FONTS: embedding fonts will usually break them |
||
53 | * in most browsers. Enable this flag to force the embed. WARNING: |
||
54 | * this flag is currently not unit tested, but seems to work. |
||
55 | * - CssEmbed::HTTP_EMBED_SVG: SVG is often used as a font face; however |
||
56 | * including these in a stylesheet will cause it to bloat for browsers |
||
57 | * that don't use it. By default SVGs will be replaced with the URL |
||
58 | * to the asset; set this flag to force the embed of SVG files. |
||
59 | * - CssEmbed::HTTP_EMBED_SCHEME: By default, assets that are converted |
||
60 | * to URLs instead of data urls have no scheme (eg, "//example.com"). |
||
61 | * This is better for stylesheets that are maybe served over http or |
||
62 | * https, but it will break stylesheets served from a local HTML file. |
||
63 | * Set this option to force the schema (eg, "http://example.com"). |
||
64 | * - CssEmbed::HTTP_EMBED_URL_ONLY: do not convert assets to data URLs, |
||
65 | * only the fully qualified URL. |
||
66 | * |
||
67 | * |
||
68 | * @param integer $flags |
||
69 | * |
||
70 | * @return void |
||
71 | */ |
||
72 | public function enableHttp($flags = null) |
||
80 | |||
81 | /** |
||
82 | * Set a single http option flag. See `enableHttp` for a description of |
||
83 | * available flags. |
||
84 | * |
||
85 | * @param integer $flag |
||
86 | * |
||
87 | * @return void |
||
88 | */ |
||
89 | public function setHttpFlag($flag) |
||
93 | |||
94 | /** |
||
95 | * unset a single http option flag. See `enableHttp` for a description of |
||
96 | * available flags. |
||
97 | * |
||
98 | * @param integer $flag |
||
99 | * |
||
100 | * @return void |
||
101 | */ |
||
102 | public function unsetHttpFlag($flag) |
||
106 | |||
107 | /** |
||
108 | * @param $css_file |
||
109 | * @return null|string |
||
110 | * @throws \InvalidArgumentException |
||
111 | */ |
||
112 | public function embedCss($css_file) |
||
127 | |||
128 | /** |
||
129 | * @param $content |
||
130 | * @return mixed |
||
131 | */ |
||
132 | public function embedString($content) |
||
143 | |||
144 | |||
145 | /** |
||
146 | * @param $matches |
||
147 | * @return string |
||
148 | */ |
||
149 | protected function replace($matches) |
||
153 | |||
154 | /** |
||
155 | * @param $file |
||
156 | * @return string |
||
157 | */ |
||
158 | protected function embedFile($file) |
||
162 | |||
163 | /** |
||
164 | * @param $file |
||
165 | * @return string |
||
166 | */ |
||
167 | protected function mimeType($file) |
||
179 | |||
180 | /** |
||
181 | * @param $file |
||
182 | * @return string |
||
183 | * @throws \InvalidArgumentException |
||
184 | */ |
||
185 | protected function base64($file) |
||
193 | |||
194 | /** |
||
195 | * @param $matches |
||
196 | * @return string |
||
197 | */ |
||
198 | protected function httpEnabledReplace($matches) |
||
215 | |||
216 | /** |
||
217 | * Get the contents of a URL and return it as a data uri within url() |
||
218 | * |
||
219 | * @param string $url the URL to the file to embed |
||
220 | * @return string|bool the string for the CSS url property, or FALSE if the |
||
221 | * url could not/should not be embedded. |
||
222 | */ |
||
223 | protected function embedHttpAsset($url) |
||
263 | |||
264 | /** |
||
265 | * For URLs that could not/should not be embedded, embed the resolved URL |
||
266 | * instead. |
||
267 | * |
||
268 | * @param string $url |
||
269 | * @return string |
||
270 | */ |
||
271 | protected function embedHttpAssetUrl($url) |
||
278 | |||
279 | /** |
||
280 | * Check if an asset is remote or local |
||
281 | * |
||
282 | * @param string $path the path specified in the CSS file |
||
283 | * |
||
284 | * @return bool |
||
285 | */ |
||
286 | protected function isHttpAsset($path) |
||
308 | |||
309 | /** |
||
310 | * Resolve the URL to an http asset |
||
311 | * |
||
312 | * @param string $root_url the root URL |
||
313 | * @param string |
||
314 | */ |
||
315 | protected function resolveHttpAssetUrl($root_url, $path) |
||
316 | { |
||
317 | $default_scheme = ($this->http_flags & self::HTTP_DEFAULT_HTTPS) |
||
318 | ? 'https:' |
||
319 | : 'http:' |
||
320 | ; |
||
321 | |||
322 | // case 1: path is already fully qualified url |
||
323 | if (strpos($path, '//') === 0) { |
||
324 | $path = $default_scheme . $path; |
||
325 | } |
||
326 | if (preg_match('/^https?:\/\//', $path)) { |
||
327 | if (!filter_var($path, FILTER_VALIDATE_URL)) { |
||
328 | $this->httpError('Invalid asset url "%s"', $path); |
||
329 | return false; |
||
330 | } |
||
331 | return $path; |
||
332 | } |
||
333 | |||
334 | if (strpos($root_url, '//') === 0) { |
||
335 | $root_url = $default_scheme . $root_url; |
||
336 | } |
||
337 | $root_domain = preg_replace('#^(https?://[^/]+).*#', '$1', $root_url); |
||
338 | $root_path = substr($root_url, strlen($root_domain)); |
||
339 | |||
340 | // case 2: asset is absolute path |
||
341 | if (strpos($path, '/') === 0) { |
||
342 | return $root_domain . $path; |
||
343 | } |
||
344 | |||
345 | // case 3: asset is relative path |
||
346 | // remove directory transversal (file_get_contents seems to choke on it) |
||
347 | $path = explode('/', $path); |
||
348 | $root_path = array_filter(explode('/', $root_path)); |
||
349 | $asset_path = array(); |
||
350 | while (NULL !== ($part = array_shift($path))) { |
||
351 | if (!$part || $part === '.') { |
||
|
|||
352 | // drop the empty part |
||
353 | } elseif ($part == '..') { |
||
354 | array_pop($root_path); |
||
355 | } else { |
||
356 | $asset_path[] = $part; |
||
357 | } |
||
358 | } |
||
359 | $asset_path = implode('/', $asset_path); |
||
360 | $root_path = empty($root_path) ? '/' : '/' . implode('/', $root_path) . '/'; |
||
361 | |||
362 | // ... and build the URL |
||
363 | $url = $root_domain . $root_path . $asset_path; |
||
364 | if (!filter_var($url, FILTER_VALIDATE_URL)) { |
||
365 | $this->httpError('Could not resolve "%s" with root "%s"', $path, $this->root_dir); |
||
366 | return false; |
||
367 | } |
||
368 | return $url; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Throw an exception if HTTP_URL_ON_ERROR is not set |
||
373 | * |
||
374 | * @param string $msg the message |
||
375 | * @param string $interpolations... strings to interpolate in the error message |
||
376 | * @throws \InvalidArgmumentException |
||
377 | * @return void |
||
378 | */ |
||
379 | protected function httpError($msg, $interpolations) |
||
387 | } |
||
388 |
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.