Completed
Push — master ( d82b6b...206636 )
by Nazar
08:39
created

Collecting   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 84.25%

Importance

Changes 0
Metric Value
dl 0
loc 314
ccs 107
cts 127
cp 0.8425
rs 8.3673
c 0
b 0
f 0
wmc 45
lcom 1
cbo 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
A get_includes_list() 0 19 3
A fill_includes() 0 5 1
A fill_includes_internal() 0 3 2
B get_includes_dependencies_and_map() 0 42 3
C process_meta() 0 44 7
A process_map() 0 6 2
B process_map_internal() 0 27 5
C normalize_dependencies() 0 51 11
A array_flatten() 0 8 3
A webcomponents_support_filter() 0 18 4
A clean_includes_arrays_without_files() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like Collecting 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 Collecting, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2014-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\Page\Includes;
9
use
10
	cs\Config,
11
	cs\Event;
12
13
trait Collecting {
14
	/**
15
	 * Getting of HTML, JS and CSS files list to be included
16
	 *
17
	 * @param Config $Config
18
	 *
19
	 * @return string[][]
20
	 */
21 4
	protected function get_includes_list ($Config) {
22 4
		$includes = [];
23
		/**
24
		 * Get includes of system and theme
25
		 */
26 4
		$this->fill_includes(DIR.'/includes', $includes);
27 4
		$this->fill_includes(THEMES."/$this->theme", $includes);
28 4
		foreach ($Config->components['modules'] as $module_name => $module_data) {
29 4
			if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) {
30
				continue;
31
			}
32 4
			$this->fill_includes(MODULES."/$module_name/includes", $includes);
33
		}
34
		return [
35 4
			'html' => array_merge(...$includes['html']),
36 4
			'js'   => array_merge(...$includes['js']),
37 4
			'css'  => array_merge(...$includes['css'])
38
		];
39
	}
40
	/**
41
	 * @param string     $base_dir
42
	 * @param string[][] $includes
43
	 */
44 4
	protected function fill_includes ($base_dir, &$includes) {
45 4
		$includes['html'][] = $this->fill_includes_internal($base_dir, 'html');
46 4
		$includes['js'][]   = $this->fill_includes_internal($base_dir, 'js');
47 4
		$includes['css'][]  = $this->fill_includes_internal($base_dir, 'css');
48 4
	}
49
	/**
50
	 * @param string $base_dir
51
	 * @param string $ext
52
	 *
53
	 * @return array
54
	 */
55 4
	protected function fill_includes_internal ($base_dir, $ext) {
56 4
		return get_files_list("$base_dir/$ext", "/.*\\.$ext\$/i", 'f', true, true, 'name', '!include') ?: [];
57
	}
58
	/**
59
	 * Get dependencies of components between each other (only that contains some HTML, JS and CSS files) and mapping HTML, JS and CSS files to URL paths
60
	 *
61
	 * @param Config $Config
62
	 *
63
	 * @return array[] [$dependencies, $includes_map]
64
	 */
65 4
	protected function get_includes_dependencies_and_map ($Config) {
66
		/**
67
		 * Get all includes
68
		 */
69 4
		$all_includes = $this->get_includes_list($Config);
70 4
		$includes_map = [];
71
		/**
72
		 * Array [package => [list of packages it depends on]]
73
		 */
74 4
		$dependencies    = [];
75 4
		$functionalities = [];
76
		/**
77
		 * According to components's maps some files should be included only on specific pages.
78
		 * Here we read this rules, and remove from whole includes list such items, that should be included only on specific pages.
79
		 * Also collect dependencies.
80
		 */
81 4
		foreach ($Config->components['modules'] as $module_name => $module_data) {
82 4
			if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) {
83
				continue;
84
			}
85 4
			$this->process_meta(MODULES."/$module_name", $dependencies, $functionalities, $module_data['active'] != Config\Module_Properties::ENABLED);
86 4
			$this->process_map(MODULES."/$module_name", $includes_map, $all_includes);
87
		}
88 4
		unset($module_name, $module_data);
89
		/**
90
		 * For consistency
91
		 */
92 4
		$includes_map['System'] = $all_includes;
93 4
		Event::instance()->fire(
94 4
			'System/Page/includes_dependencies_and_map',
95
			[
96 4
				'dependencies' => &$dependencies,
97 4
				'includes_map' => &$includes_map
98
			]
99
		);
100 4
		$includes_map = $this->webcomponents_support_filter($Config, $includes_map);
101 4
		$dependencies = $this->normalize_dependencies($dependencies, $functionalities);
102 4
		$includes_map = $this->clean_includes_arrays_without_files($dependencies, $includes_map);
103 4
		$dependencies = array_map('array_values', $dependencies);
104 4
		$dependencies = array_filter($dependencies);
105 4
		return [$dependencies, $includes_map];
106
	}
107
	/**
108
	 * Process meta information and corresponding entries to dependencies and functionalities
109
	 *
110
	 * @param string $base_dir
111
	 * @param array  $dependencies
112
	 * @param array  $functionalities
113
	 * @param bool   $skip_functionalities
114
	 */
115 4
	protected function process_meta ($base_dir, &$dependencies, &$functionalities, $skip_functionalities = false) {
116 4
		if (!file_exists("$base_dir/meta.json")) {
117
			return;
118
		}
119 4
		$meta = file_get_json("$base_dir/meta.json");
120
		$meta += [
121 4
			'require'  => [],
122
			'optional' => [],
123
			'provide'  => []
124
		];
125 4
		$package = $meta['package'];
126 4
		foreach ((array)$meta['require'] as $r) {
127
			/**
128
			 * Get only name of package or functionality
129
			 */
130
			$r                        = preg_split('/[=<>]/', $r, 2)[0];
131
			$dependencies[$package][] = $r;
132
		}
133 4
		foreach ((array)$meta['optional'] as $o) {
134
			/**
135
			 * Get only name of package or functionality
136
			 */
137 4
			$o                        = preg_split('/[=<>]/', $o, 2)[0];
138 4
			$dependencies[$package][] = $o;
139
		}
140 4
		if ($skip_functionalities) {
141
			return;
142
		}
143 4
		foreach ((array)$meta['provide'] as $p) {
144
			/**
145
			 * If provides sub-functionality for other component (for instance, `Blog/post_patch`) - inverse "providing" to "dependency"
146
			 * Otherwise it is just functionality alias to package name
147
			 */
148 4
			if (strpos($p, '/') !== false) {
149
				/**
150
				 * Get name of package or functionality
151
				 */
152
				$p                  = explode('/', $p)[0];
153
				$dependencies[$p][] = $package;
154
			} else {
155 4
				$functionalities[$p] = $package;
156
			}
157
		}
158 4
	}
159
	/**
160
	 * Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages)
161
	 *
162
	 * @param string $base_dir
163
	 * @param array  $includes_map
164
	 * @param array  $all_includes
165
	 */
166 4
	protected function process_map ($base_dir, &$includes_map, &$all_includes) {
167 4
		if (!file_exists("$base_dir/includes/map.json")) {
168
			return;
169
		}
170 4
		$this->process_map_internal(file_get_json("$base_dir/includes/map.json"), "$base_dir/includes", $includes_map, $all_includes);
171 4
	}
172
	/**
173
	 * Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages)
174
	 *
175
	 * @param array  $map
176
	 * @param string $includes_dir
177
	 * @param array  $includes_map
178
	 * @param array  $all_includes
179
	 */
180 4
	protected function process_map_internal ($map, $includes_dir, &$includes_map, &$all_includes) {
181 4
		foreach ($map as $path => $files) {
182 4
			foreach ((array)$files as $file) {
183 4
				$extension = file_extension($file);
184 4
				if (in_array($extension, ['css', 'js', 'html'])) {
185 4
					$file                              = "$includes_dir/$extension/$file";
186 4
					$includes_map[$path][$extension][] = $file;
187 4
					$all_includes[$extension]          = array_diff($all_includes[$extension], [$file]);
188
				} else {
189 4
					$file = rtrim($file, '*');
190
					/**
191
					 * Wildcard support, it is possible to specify just path prefix and all files with this prefix will be included
192
					 */
193 4
					$found_files = array_filter(
194 4
						get_files_list($includes_dir, '/.*\.(css|js|html)$/i', 'f', '', true, 'name', '!include') ?: [],
195
						function ($f) use ($file) {
196
							// We need only files with specified mask and only those located in directory that corresponds to file's extension
197 4
							return preg_match("#^(css|js|html)/$file.*\\1$#i", $f);
198 4
						}
199
					);
200
					// Drop first level directory
201 4
					$found_files = _preg_replace('#^[^/]+/(.*)#', '$1', $found_files);
202 4
					$this->process_map_internal([$path => $found_files], $includes_dir, $includes_map, $all_includes);
203
				}
204
			}
205
		}
206 4
	}
207
	/**
208
	 * Replace functionalities by real packages names, take into account recursive dependencies
209
	 *
210
	 * @param array $dependencies
211
	 * @param array $functionalities
212
	 *
213
	 * @return array
214
	 */
215 4
	protected function normalize_dependencies ($dependencies, $functionalities) {
216
		/**
217
		 * First of all remove packages without any dependencies
218
		 */
219 4
		$dependencies = array_filter($dependencies);
220
		/**
221
		 * First round, process aliases among keys
222
		 */
223 4
		foreach (array_keys($dependencies) as $d) {
224 4
			if (isset($functionalities[$d])) {
225
				$package = $functionalities[$d];
226
				/**
227
				 * Add dependencies to existing package dependencies
228
				 */
229
				foreach ($dependencies[$d] as $dependency) {
230
					$dependencies[$package][] = $dependency;
231
				}
232
				/**
233
				 * Drop alias
234
				 */
235 4
				unset($dependencies[$d]);
236
			}
237
		}
238 4
		unset($d, $dependency);
239
		/**
240
		 * Second round, process aliases among dependencies
241
		 */
242 4
		foreach ($dependencies as &$depends_on) {
243 4
			foreach ($depends_on as &$dependency) {
244 4
				if (isset($functionalities[$dependency])) {
245 4
					$dependency = $functionalities[$dependency];
246
				}
247
			}
248 4
			unset($dependency);
249
		}
250 4
		unset($depends_on);
251
		/**
252
		 * Third round, build dependencies tree using references to corresponding recursive dependencies
253
		 */
254 4
		foreach ($dependencies as &$depends_on) {
255 4
			foreach ($depends_on as &$dependency) {
256 4
				if ($dependency != 'System' && isset($dependencies[$dependency])) {
257 4
					$dependency = [&$dependencies[$dependency], $dependency];
258
				}
259
			}
260 4
			unset($dependency);
261
		}
262 4
		unset($depends_on);
263 4
		$dependencies = array_map([$this, 'array_flatten'], $dependencies);
264 4
		return array_map('array_unique', $dependencies);
265
	}
266
	/**
267
	 * Convert array of arbitrary nested structure into flat array
268
	 *
269
	 * @param array $array
270
	 *
271
	 * @return string[]
272
	 */
273 4
	protected function array_flatten ($array) {
274 4
		foreach ($array as &$a) {
275 4
			if (is_array($a)) {
276 4
				$a = $this->array_flatten($a);
277
			}
278
		}
279 4
		return array_merge(..._array($array));
280
	}
281
	/**
282
	 * If system is configured to not use Web Components - all HTML imports and Polymer-related JS code will be removed from includes map
283
	 *
284
	 * @param Config  $Config
285
	 * @param array[] $includes_map
286
	 *
287
	 * @return array[]
288
	 */
289 4
	protected function webcomponents_support_filter ($Config, $includes_map) {
290 4
		if ($this->theme != Config::SYSTEM_THEME && $Config->core['disable_webcomponents']) {
291
			foreach ($includes_map as &$includes) {
292
				unset($includes['html']);
293
			}
294
			unset($includes);
295
			$prefix                                    = DIR.'/includes/js/Polymer';
296
			$includes_map[Config::SYSTEM_MODULE]['js'] = array_values(
297
				array_filter(
298
					$includes_map[Config::SYSTEM_MODULE]['js'],
299
					function ($file) use ($prefix) {
300
						return strpos($file, $prefix) !== 0;
301
					}
302
				)
303
			);
304
		}
305 4
		return $includes_map;
306
	}
307
	/**
308
	 * Includes array is composed from dependencies and sometimes dependencies doesn't have any files, so we'll clean that
309
	 *
310
	 * @param array $dependencies
311
	 * @param array $includes_map
312
	 *
313
	 * @return array
314
	 */
315 4
	protected function clean_includes_arrays_without_files ($dependencies, $includes_map) {
316 4
		foreach ($dependencies as &$depends_on) {
317 4
			foreach ($depends_on as $index => &$dependency) {
318 4
				if (!isset($includes_map[$dependency])) {
319 4
					unset($depends_on[$index]);
320
				}
321
			}
322 4
			unset($dependency);
323
		}
324 4
		return $includes_map;
325
	}
326
}
327