Completed
Push — master ( 5c0dbc...42c9a9 )
by Nazar
04:27
created

Data_model_processing   C

Complexity

Total Complexity 61

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 291
ccs 137
cts 137
cp 1
rs 6.018
c 0
b 0
f 0
wmc 61
lcom 2
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
cdb() 0 1 ?
A find_update_files_tags() 0 12 4
B update_files_tags() 0 22 5
A delete_files_tags() 0 11 2
A fix_arguments_order() 0 12 4
C crud_arguments_preparation() 0 46 9
D prepare_joined_tables_model() 0 34 10
C crud_argument_preparation() 0 50 15
A is_multilingual() 0 3 2
A with_files_support() 0 3 2
A recursive_implode() 0 8 3
B find_urls() 0 24 5

How to fix   Complexity   

Complex Class

Complex classes like Data_model_processing 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 Data_model_processing, 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) 2013-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\CRUD;
9
use
10
	cs\Event,
11
	cs\Language,
12
	cs\Text;
13
14
/**
15
 * @property array  $data_model
16
 * @property string $data_model_ml_group
17
 * @property string $data_model_files_tag_prefix
18
 * @property string $table
19
 */
20
trait Data_model_processing {
21
	/**
22
	 * @return int
23
	 */
24
	abstract protected function cdb ();
25
	/**
26
	 * @param callable[]|string[] $data_model
27
	 * @param array               $arguments
28
	 *
29
	 * @return array
30
	 */
31 32
	private function fix_arguments_order ($data_model, $arguments) {
32 32
		if (is_array_indexed($arguments)) {
33 12
			return $arguments;
34
		}
35 26
		$arguments_ = [];
36 26
		foreach (array_keys($data_model) as $argument) {
37 26
			if (array_key_exists($argument, $arguments)) {
38 26
				$arguments_[] = $arguments[$argument];
39
			}
40
		}
41 26
		return $arguments_;
42
	}
43
	/**
44
	 * @param callable[]|string[] $data_model
45
	 * @param array               $arguments
46
	 * @param false|int           $id            On update id should be specified to work properly with multilingual fields
47
	 * @param bool                $update_needed If on creation request without specified primary key and multilingual fields present - update needed
48
	 *                                           after creation (there is no id before creation)
49
	 *
50
	 * @return array[]
51
	 */
52 32
	private function crud_arguments_preparation ($data_model, $arguments, $id = false, &$update_needed = false) {
53 32
		$arguments     = array_combine(array_keys($data_model), $arguments);
54 32
		$joined_tables = [];
55 32
		foreach ($arguments as $item => &$argument) {
56 32
			$model = $data_model[$item];
57 32
			if (is_callable($model)) {
58 2
				$argument = $model($argument);
59 2
				continue;
60
			}
61 32
			if (isset($model['data_model'])) {
62 4
				$joined_tables[$item] = $this->prepare_joined_tables_model($model, $argument);
63 4
				unset($arguments[$item]);
64 4
				continue;
65
			}
66 32
			$model              = explode(':', $model, 2);
67 32
			$type               = $model[0];
68 32
			$multilingual_field = false;
69
			/**
70
			 * If field is multilingual
71
			 */
72 32
			if ($type == 'ml') {
73 2
				$multilingual_field = true;
74 2
				$model              = explode(':', $model[1], 2);
75 2
				$type               = $model[0];
76
			}
77 32
			$argument = $this->crud_argument_preparation(
78
				$type,
79 32
				isset($model[1]) ? $model[1] : null,
80
				$argument
81
			);
82
			/**
83
			 * If field is multilingual - handle multilingual storing of value automatically
84
			 */
85 32
			if ($multilingual_field && $this->is_multilingual()) {
86 2
				if ($id !== false) {
87 2
					$argument = Text::instance()->set($this->cdb(), "$this->data_model_ml_group/$item", $id, $argument);
88
				} else {
89 32
					$update_needed = true;
90
				}
91
			}
92
		}
93
		return [
94 32
			$arguments,
95 32
			$joined_tables
96
		];
97
	}
98
	/**
99
	 * @param array $structure
100
	 * @param array $arguments
101
	 *
102
	 * @return array
103
	 */
104 4
	private function prepare_joined_tables_model ($structure, $arguments) {
105 4
		if (!$arguments) {
106 2
			return [];
107
		}
108
		$new_structure = [
109 4
			'id_field' => array_keys($structure['data_model'])[0],
110 4
			'fields'   => array_slice($structure['data_model'], 1),
111
			'data'     => []
112
		];
113 4
		if (isset($structure['language_field'])) {
114 2
			$new_structure['language_field'] = $structure['language_field'];
115
		}
116 4
		$arguments       = is_array_assoc($arguments) ? [$arguments] : _array((array)$arguments);
117 4
		$arguments_assoc = is_array_assoc($arguments[0]);
118 4
		foreach ($new_structure['fields'] as $field_name => $field_model) {
119
			/**
120
			 * Both associative and indexed arrays are supported - that is why we determine key for array
121
			 */
122 4
			$key = $arguments_assoc ? $field_name : array_search($field_name, array_keys($new_structure['fields']));
123 4
			if (is_callable($field_model)) {
124 2
				foreach ($arguments as $index => $arguments_local) {
125 2
					$new_structure['data'][$index][$field_name] = $field_model($arguments_local[$key]);
126
				}
127 2
				continue;
128
			}
129 4
			$field_model = explode(':', $field_model, 2);
130 4
			$type        = $field_model[0];
131 4
			$format      = isset($field_model[1]) ? $field_model[1] : null;
132 4
			foreach ($arguments as $index => $arguments_local) {
133 4
				$new_structure['data'][$index][$field_name] = $this->crud_argument_preparation($type, $format, $arguments_local[$key]);
134
			}
135
		}
136 4
		return $new_structure;
137
	}
138
	/**
139
	 * @param string $type
140
	 * @param mixed  $format
141
	 * @param mixed  $argument
142
	 *
143
	 * @return float|int|mixed|string|\string[]
144
	 */
145 32
	private function crud_argument_preparation ($type, $format, $argument) {
146
		switch ($type) {
147 32
			case 'int':
148 32
			case 'float':
149 26
				$argument = $type == 'int' ? (int)$argument : (float)$argument;
150
				/**
151
				 * Ranges processing
152
				 */
153 26
				if ($format !== null) {
154
					/**
155
					 * After this `$format[0]` will contain minimum and `$format[1]` if exists - maximum
156
					 */
157 26
					$format   = explode('..', $format);
158 26
					$argument = max($argument, $format[0]);
159 26
					if (isset($format[1])) {
160 2
						$argument = min($argument, $format[1]);
161
					}
162
				}
163 26
				break;
164 32
			case 'text':
165 30
			case 'html':
166 28
			case 'html_iframe':
167 30
				$argument = xap(
168
					$argument,
169 30
					$type == 'text' ? 'text' : true,
170 30
					$type == 'html_iframe'
171
				);
172
				/**
173
				 * Truncation
174
				 */
175 30
				if ($format !== null) {
176
					/**
177
					 * After this `$format[0]` will contain length to truncation and `$format[1]` if exists - ending
178
					 */
179 2
					$format   = explode(':', $format);
180 2
					$argument = truncate($argument, $format[0], isset($format[1]) ? $format[1] : '...', true);
181
				}
182 30
				break;
183 28
			case 'set':
184 2
				$allowed_arguments = explode(',', $format);
185 2
				if (!in_array($argument, $allowed_arguments)) {
186 2
					$argument = $allowed_arguments[0];
187
				}
188 2
				break;
189 28
			case 'json':
190 26
				$argument = _json_encode($argument);
191 26
				break;
192
		}
193 32
		return $argument;
194
	}
195
	/**
196
	 * @return bool
197
	 */
198 14
	private function is_multilingual () {
199 14
		return isset($this->data_model_ml_group) && $this->data_model_ml_group;
200
	}
201
	/**
202
	 * @return bool
203
	 */
204 32
	private function with_files_support () {
205 32
		return isset($this->data_model_files_tag_prefix) && $this->data_model_files_tag_prefix;
206
	}
207
	/**
208
	 * @param int|string     $id
209
	 * @param int[]|string[] $data_before
210
	 * @param int[]|string[] $data_after
211
	 */
212 32
	private function find_update_files_tags ($id, $data_before, $data_after) {
213 32
		if (!$this->with_files_support()) {
214 30
			return;
215
		}
216 2
		$prefix = $this->data_model_files_tag_prefix;
217 2
		$clang  = Language::instance()->clang;
218 2
		$this->update_files_tags(
219 2
			"$prefix/$id/$clang",
220 2
			$this->find_urls($data_before ?: []),
221 2
			$this->find_urls($data_after ?: [])
222
		);
223 2
	}
224
	/**
225
	 * Find URLs (any actually) in attributes values (wrapped with `"`, other quotes are not supported) or if field itself is URL
226
	 *
227
	 * @param array $data
228
	 *
229
	 * @return string[]
230
	 */
231 2
	protected function find_urls ($data) {
232
		/**
233
		 * At first we search URLs among attributes values, then whether some field looks like URL itself, and lastly do recursive scan
234
		 */
235 2
		return array_merge(
236 2
			preg_match_all('/"((http[s]?:)?\/\/.+)"/Uims', $this->recursive_implode(' ', $data), $files)
237 2
				? array_unique($files[1])
238 2
				: [],
239
			array_filter(
240
				$data,
241
				function ($data) {
242 2
					return !is_array($data) && preg_match('/^(http[s]?:)?\/\/.+$/Uims', $data);
243 2
				}
244
			),
245 2
			$data ? array_merge(
246 2
				... array_map(
247 2
						function ($data) {
248 2
							return is_array($data) ? $this->find_urls($data) : [];
249 2
						},
250
						array_values($data)
251
					)
252 2
			) : []
253
		);
254
	}
255
	/**
256
	 * @param string $glue
257
	 * @param array  $pieces
258
	 *
259
	 * @return string
260
	 */
261 2
	protected function recursive_implode ($glue, $pieces) {
262 2
		foreach ($pieces as &$p) {
263 2
			if (is_array($p)) {
264 2
				$p = $this->recursive_implode($glue, $p);
265
			}
266
		}
267 2
		return implode($glue, $pieces);
268
	}
269
	/**
270
	 * @param string   $tag
271
	 * @param string[] $old_files
272
	 * @param string[] $new_files
273
	 */
274 2
	protected function update_files_tags ($tag, $old_files, $new_files) {
275 2
		if ($old_files || $new_files) {
276 2
			foreach (array_diff($old_files, $new_files) as $file) {
277 2
				Event::instance()->fire(
278 2
					'System/upload_files/del_tag',
279
					[
280 2
						'tag' => $tag,
281 2
						'url' => $file
282
					]
283
				);
284
			}
285 2
			foreach (array_diff($new_files, $old_files) as $file) {
286 2
				Event::instance()->fire(
287 2
					'System/upload_files/add_tag',
288
					[
289 2
						'tag' => $tag,
290 2
						'url' => $file
291
					]
292
				);
293
			}
294
		}
295 2
	}
296
	/**
297
	 * @param int|string $id
298
	 */
299 14
	private function delete_files_tags ($id) {
300 14
		if (!$this->with_files_support()) {
301 12
			return;
302
		}
303 2
		Event::instance()->fire(
304 2
			'System/upload_files/del_tag',
305
			[
306 2
				'tag' => "$this->data_model_files_tag_prefix/$id%"
307
			]
308
		);
309 2
	}
310
}
311