Legacy_Storage_Service_v_1_5   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 471
Duplicated Lines 3.61 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 9.27%

Importance

Changes 0
Metric Value
dl 17
loc 471
ccs 22
cts 237
cp 0.0927
rs 3.44
c 0
b 0
f 0
wmc 62
lcom 1
cbo 8

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A enabled() 0 3 1
A disabled() 0 3 1
A is_legacy_map_key() 0 7 1
A get_container_for_datastore() 0 9 3
A get_field_group_permutations() 0 28 5
A get_table_details_for_datastore() 0 25 3
A get_legacy_sql_comparisons_for_field() 0 22 5
B get_legacy_storage_array_from_database() 0 38 7
A get_legacy_storage_array() 0 14 3
A get_value_for_legacy_key() 0 16 4
A legacy_storage_rows_to_row_descriptors() 0 14 3
A get_key_segmentation_regex_for_field_name() 0 9 3
B legacy_storage_row_to_row_descriptor() 0 35 6
B row_descriptor_to_storage_array() 9 55 8
A get_storage_array_for_datastore() 0 17 3
A get_storage_array() 8 13 3
A filter_storage_array() 0 6 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
namespace Carbon_Fields\Service;
4
5
use Carbon_Fields\Field\Field;
6
use Carbon_Fields\Container\Container;
7
use Carbon_Fields\Container\Repository as ContainerRepository;
8
use Carbon_Fields\Value_Set\Value_Set;
9
use Carbon_Fields\Toolset\Key_Toolset;
10
use Carbon_Fields\Datastore\Datastore_Interface;
11
12
/*
13
 * Service which provides the ability to do meta queries for multi-value fields and nested fields
14
 */
15
class Legacy_Storage_Service_v_1_5 extends Service {
16
17
	/**
18
	 * Contaier repository to fetch fields from
19
	 *
20
	 * @var ContainerRepository
21
	 */
22
	protected $container_repository;
23
24
	/**
25
	 * Key Toolset for key generation and comparison utilities
26
	 *
27
	 * @var Key_Toolset
28
	 */
29
	protected $key_toolset;
30
31
	/**
32
	 * List of special key suffixes that the Map field uses so save extra data
33
	 *
34
	 * @var array
35
	 */
36
	protected $map_keys = array( 'lat', 'lng', 'zoom', 'address' );
37
38
	/**
39
	 * Cache of converted storage arrays
40
	 *
41
	 * @var array
42
	 */
43
	protected $storage_array_cache = array();
44
45
	/**
46
	 * Service constructor
47
	 *
48
	 * @param ContainerRepository $container_repository
49
	 * @param Key_Toolset         $key_toolset
50
	 */
51
	public function __construct( ContainerRepository $container_repository, Key_Toolset $key_toolset ) {
52
		$this->container_repository = $container_repository;
53
		$this->key_toolset = $key_toolset;
54
	}
55
56
	/**
57
	 * Enable the service
58
	 */
59
	protected function enabled() {
60
		add_filter( 'carbon_fields_datastore_storage_array', array( $this, 'filter_storage_array' ), 10, 3 );
61
	}
62
63
	/**
64
	 * Disable the service
65
	 */
66
	protected function disabled() {
67
		remove_filter( 'carbon_fields_datastore_storage_array', array( $this, 'filter_storage_array' ), 10 );
68
	}
69
70
	/**
71
	 * Check if a key is a legacy map property key
72
	 *
73
	 * @param string $key
74
	 * @return bool
75
	 */
76
	protected function is_legacy_map_key( $key ) {
77
		$map_regex = array_map( function( $map_key ) {
78
			return preg_quote( $map_key, '/' );
79
		}, $this->map_keys );
80
		$map_regex = '/-(' . implode( '|', $map_regex ) . ')$/';
81
		return (bool) preg_match( $map_regex, $key );
82
	}
83
84
	/**
85
	 * Return container instance which uses the passed datastore
86
	 *
87
	 * @param  Datastore_Interface $datastore
88
	 * @return Container
89
	 */
90
	protected function get_container_for_datastore( Datastore_Interface $datastore ) {
91
		$containers = $this->container_repository->get_containers();
92
		foreach ( $containers as $container ) {
93
			if ( $container->get_datastore() === $datastore ) {
94
				return $container;
95
			}
96
		}
97
		return null;
98
	}
99
100
	/**
101
	 * Get a nested array of field_group permutations suitable for old key parsing
102
	 *
103
	 * @param  array $fields
104
	 * @return array
105
	 */
106
	protected function get_field_group_permutations( $fields ) {
107
		$permutations = array();
108
109
		foreach ( $fields as $field ) {
110
			if ( $field instanceof \Carbon_Fields\Field\Complex_Field ) {
111
				$group_names = $field->get_group_names();
112
				foreach ( $group_names as $group_name ) {
113
					$group = $field->get_group_by_name( $group_name );
114
					if ( ! $group ) {
115
						continue;
116
					}
117
					$permutations[] = array(
118
						'field' => $field->get_base_name(),
119
						'group' => $group_name,
120
						'children' => $this->get_field_group_permutations( $group->get_fields() ),
121
					);
122
				}
123
			} else {
124
				$permutations[] = array(
125
					'field' => $field->get_base_name(),
126
					'group' => '',
127
					'children' => array(),
128
				);
129
			}
130
		}
131
132
		return $permutations;
133
	}
134
135
	/**
136
	 * Get array of database table details for datastore
137
	 *
138
	 * @param Datastore_Interface $datastore
139
	 * @return array
140
	 */
141
	protected function get_table_details_for_datastore( Datastore_Interface $datastore ) {
142
		global $wpdb;
143
144
		$details = array(
145
			'prefix' => '_',
146
			'table_name' => '',
147
			'table_id_column' => '',
148
			'table_key_column' => '',
149
			'table_value_column' => '',
150
		);
151
152
		if ( $datastore instanceof \Carbon_Fields\Datastore\Theme_Options_Datastore ) {
153
			$details['prefix'] = '';
154
			$details['table_name'] = $wpdb->options;
155
			$details['table_key_column'] = 'option_name';
156
			$details['table_value_column'] = 'option_value';
157
		} else if ( $datastore instanceof \Carbon_Fields\Datastore\Meta_Datastore ) {
158
			$details['table_name'] = $datastore->get_table_name();
159
			$details['table_id_column'] = $datastore->get_table_field_name();
160
			$details['table_key_column'] = 'meta_key';
161
			$details['table_value_column'] = 'meta_value';
162
		}
163
164
		return $details;
165
	}
166
167
	/**
168
	 * Get array of sql comparisons for field
169
	 *
170
	 * @param  Field  $field
171
	 * @param  string $key_prefix
172
	 * @param  string $key_column
173
	 * @return array<string>
174
	 */
175
	protected function get_legacy_sql_comparisons_for_field( Field $field, $key_prefix, $key_column ) {
176
		$field_key_pattern = $key_prefix . $field->get_base_name();
177
		$comparisons = array();
178
179
		if ( $field instanceof \Carbon_Fields\Field\Complex_Field ) {
180
			$groups = $field->get_group_names();
181
			foreach ( $groups as $group_name ) {
182
				$underscored_group_name = preg_replace( '/^_{0,1}/', '_', $group_name ); // ensure first character is underscore
183
				$comparisons[] = ' `' . $key_column . '` LIKE "' . esc_sql( $field_key_pattern . $underscored_group_name . '-' ) . '%" ';
184
			}
185
		} else {
186
			$comparisons[] = ' `' . $key_column . '` = "' . esc_sql( $field_key_pattern ) . '" ';
187
		}
188
189
		if ( $field instanceof \Carbon_Fields\Field\Map_Field ) {
190
			foreach ( $this->map_keys as $map_key ) {
191
				$comparisons[] = ' `' . $key_column . '` = "' . esc_sql( $field_key_pattern . '-' . $map_key ) . '" ';
192
			}
193
		}
194
195
		return $comparisons;
196
	}
197
198
	/**
199
	 * Get a key-value array of legacy values for fields in the container of the passed datastore
200
	 *
201
	 * @param  Container           $container
202
	 * @param  Datastore_Interface $datastore
203
	 * @return array
204
	 */
205
	protected function get_legacy_storage_array_from_database( Container $container, Datastore_Interface $datastore ) {
206
		global $wpdb;
207
208
		$table = $this->get_table_details_for_datastore( $datastore );
209
210
		if ( $table['table_id_column'] && ! $datastore->get_object_id() ) {
211
			return array(); // bail as we have an ID column but no ID to compare with ( e.g. we are in a "create" view )
212
		}
213
214
		$comparisons = array();
215
		$container_fields = $container->get_fields();
216
		foreach ( $container_fields as $field ) {
217
			$comparisons = array_merge( $comparisons, $this->get_legacy_sql_comparisons_for_field( $field, $table['prefix'], $table['table_key_column'] ) );
218
		}
219
220
		if ( empty( $comparisons ) ) {
221
			return array(); // no comparisons to fetch with
222
		}
223
224
		$where_clause = ' ( ' . implode( ' OR ', $comparisons ) . ' ) ';
225
		if ( $table['table_id_column'] ) {
226
			$where_clause = ' `' . $table['table_id_column'] . '` = ' . $datastore->get_object_id() . ' AND ' . $where_clause;
227
		}
228
		$query = '
229
			SELECT `' . $table['table_key_column'] . '` AS `key`, `' . $table['table_value_column'] . '` AS `value`
230
			FROM `' . $table['table_name'] . '`
231
			WHERE ' . $where_clause . '
232
		';
233
234
		$raw_results = $wpdb->get_results( $query );
235
236
		$results = array();
237
		foreach ( $raw_results as $result ) {
238
			$results[ $result->key ] = $result->value;
239
		}
240
241
		return $results;
242
	}
243
244
	/**
245
	 * Get a key-value array of CF 1.5 values for fields in the container of the passed datastore
246
	 *
247
	 * @param  Datastore_Interface $datastore
248
	 * @return array
249
	 */
250
	public function get_legacy_storage_array( Datastore_Interface $datastore ) {
251
		$container = $this->get_container_for_datastore( $datastore );
252
		if ( ! $container ) {
253
			return array(); // unhandled datastore type or no registered containers
254
		}
255
256
		$cache_key = $container->get_id() . '-' . strval( $datastore->get_object_id() );
257
258
		if ( ! isset( $this->storage_array_cache[ $cache_key ] ) ) {
259
			$this->storage_array_cache[ $cache_key ] = $this->get_legacy_storage_array_from_database( $container, $datastore );
260
		}
261
262
		return $this->storage_array_cache[ $cache_key ];
263
	}
264
265
	/**
266
	 * Get expanded value for key from legacy storage array
267
	 *
268
	 * @param string $key Legacy key to fetch additional values for
269
	 * @param array $legacy_storage_array key=>value array of legacy data
270
	 * @return mixed
271
	 */
272
	protected function get_value_for_legacy_key( $key, $legacy_storage_array ) {
273
		$value = isset( $legacy_storage_array[ $key ] ) ? $legacy_storage_array[ $key ] : '';
274
275
		$first_map_key = $this->map_keys[0];
276
		if ( isset( $legacy_storage_array[ $key . '-' . $first_map_key ] ) ) {
277
			$value = array(
278
				Value_Set::VALUE_PROPERTY => $value,
279
			);
280
281
			foreach ( $this->map_keys as $map_key ) {
282
				$value[ $map_key ] = $legacy_storage_array[ $key . '-' . $map_key ];
283
			}
284
		}
285
286
		return $value;
287
	}
288
289
	/**
290
	 * Convert legacy storage rows to array of row descriptors
291
	 *
292
	 * @param array $legacy_storage_array
293
	 * @param array $field_group_permutations
294
	 * @return array
295
	 */
296
	protected function legacy_storage_rows_to_row_descriptors( $legacy_storage_array, $field_group_permutations ) {
297
		$row_descriptors = array();
298
299
		foreach ( $legacy_storage_array as $key => $value ) {
300
			if ( $this->is_legacy_map_key( $key ) ) {
301
				continue; // skip legacy map keys as they are handled separately through values
302
			}
303
304
			$value = $this->get_value_for_legacy_key( $key, $legacy_storage_array );
305
			$row_descriptors[] = $this->legacy_storage_row_to_row_descriptor( $key, $value, $field_group_permutations );
306
		}
307
308
		return $row_descriptors;
309
	}
310
311
	/**
312
	 * Get key segmentation regex for a field name
313
	 *
314
	 * @param  string $field_name
315
	 * @param  string $group_name
316
	 * @return string
317
	 */
318
	protected function get_key_segmentation_regex_for_field_name( $field_name, $group_name = '' ) {
319
		$legacy_group_name = ( $group_name === '_' ) ? $group_name : '_' . $group_name;
320
321
		$regex = '/\A_?' . preg_quote( $field_name, '/' ) . '(?:_(?P<group_index>\d+))?\z/';
322
		if ( $group_name !== '' ) {
323
			$regex = '/\A_?' . preg_quote( $field_name, '/' ) . '(?:_(?P<group_index>\d+))?' . preg_quote( $legacy_group_name, '/' ) . '\z/';
324
		}
325
		return $regex;
326
	}
327
328
	/**
329
	 * Convert legacy storage row to row descriptor
330
	 *
331
	 * @param  string $key
332
	 * @param  string $value
333
	 * @param  array $field_group_permutations
334
	 * @return array
335
	 */
336
	protected function legacy_storage_row_to_row_descriptor( $key, $value, $field_group_permutations ) {
337
		$key_pieces = explode( '-', $key );
338
		$field_group_level = $field_group_permutations;
339
		$matched_fields = array();
340
341
		foreach ( $key_pieces as $piece ) {
342
			foreach ( $field_group_level as $permutation ) {
343
				$match_regex = $this->get_key_segmentation_regex_for_field_name( $permutation['field'], $permutation['group'] );
344
345
				$matches = array();
346
				if ( preg_match( $match_regex, $piece, $matches ) ) {
347
					$match_data = array(
348
						'field' => $permutation['field'],
349
						'group' => $permutation['group'],
350
						'group_index' => ( isset( $matches['group_index'] ) ? intval( $matches['group_index'] ) : -1 ),
351
					);
352
353
					$previous_match_index = count( $matched_fields ) - 1;
354
					if ( isset( $matched_fields[ $previous_match_index ] ) ) {
355
						$matched_fields[ $previous_match_index ]['group_index'] = $match_data['group_index']; // indexes are offset in CF 1.5 complex keys
356
						$match_data['group_index'] = 0;
357
					}
358
					$matched_fields[] = $match_data;
359
360
					$field_group_level = $permutation['children'];
361
					break; // break so next piece foreaches the new field_group_level
362
				}
363
			}
364
		}
365
366
		return array(
367
			'key' => $matched_fields,
368
			'value' => $value,
369
		);
370
	}
371
372
	/**
373
	 * Convert row descriptor to array of new storage key-values
374
	 *
375
	 * @param  array $row_descriptor
376
	 * @return array
377
	 */
378
	protected function row_descriptor_to_storage_array( $row_descriptor ) {
379
		$storage_array = array();
380
381
		$hierarchy = array_map( function( $match ) {
382
			return $match['field'];
383
		}, $row_descriptor['key'] );
384
385
		$hierarchy_index = array_map( function( $match ) {
386
			return $match['group_index'];
387
		}, array_slice( $row_descriptor['key'], 0, -1 ) ); // last index is not part of the hierarchy index
388
389
		$complex_parents = array_slice( $hierarchy, 0, -1 );
390
		foreach ( $complex_parents as $level => $complex_parent ) {
391
			$complex_parent_hierarchy_index = array_slice( $hierarchy_index, 0, $level );
392
			$complex_parent_value_index = $hierarchy_index[ $level ];
393
394
			$key = $this->key_toolset->get_storage_key( false, array_slice( $hierarchy, 0, $level + 1 ), $complex_parent_hierarchy_index, $complex_parent_value_index, Value_Set::VALUE_PROPERTY );
395
			$group_name = $row_descriptor['key'][ $level ]['group'];
396
			$storage_array[ $key ] = $group_name;
397
		}
398
399
		$unserialized_value = maybe_unserialize( $row_descriptor['value'] );
400
		if ( is_array( $unserialized_value ) ) {
401
			if ( isset( $unserialized_value[ Value_Set::VALUE_PROPERTY ] ) ) {
402
				// value is a key=>value array - save each property separately
403 View Code Duplication
				foreach ( $unserialized_value as $property => $value_item ) {
404
					$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, $property );
405
					$storage_array[ $key ] = $value_item;
406
				}
407
			} else {
408
				// value is a simple array - save each value separately
409
				$i = 0;
410 View Code Duplication
				foreach ( $unserialized_value as $value_item ) {
411
					$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, $i, Value_Set::VALUE_PROPERTY );
412
					$storage_array[ $key ] = $value_item;
413
					$i++;
414
				}
415
			}
416
		} else if ( $unserialized_value === null ) {
417
			// no value found - add a keepalive key
418
			$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, '_empty' );
419
			$storage_array[ $key ] = '';
420
		} else {
421
			if ( count( $hierarchy ) === 1 ) {
422
				// Add a basic key as well as simple root fields will load that instead
423
				$key = '_' . $hierarchy[0];
424
				$storage_array[ $key ] = $row_descriptor['value'];
425
			}
426
427
			$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, Value_Set::VALUE_PROPERTY );
428
			$storage_array[ $key ] = $row_descriptor['value'];
429
		}
430
431
		return $storage_array;
432
	}
433
434
	/**
435
	 * Get all data saved for a datastore in the new key-value format
436
	 *
437
	 * @param  Datastore_Interface $datastore
438
	 * @return array
439
	 */
440 1
	public function get_storage_array_for_datastore( Datastore_Interface $datastore ) {
441 1
		$legacy_storage_array = $this->get_legacy_storage_array( $datastore );
442 1
		if ( empty( $legacy_storage_array ) ) {
443
			return array(); // no migration data found
444
		}
445
446 1
		$container = $this->get_container_for_datastore( $datastore );
447 1
		$field_group_permutations = $this->get_field_group_permutations( $container->get_fields() );
448 1
		$row_descriptors = $this->legacy_storage_rows_to_row_descriptors( $legacy_storage_array, $field_group_permutations );
449
450 1
		$storage_array = array();
451 1
		foreach ( $row_descriptors as $row_descriptor ) {
452 1
			$storage_array = array_merge( $storage_array, $this->row_descriptor_to_storage_array( $row_descriptor ) );
453 1
		}
454
455 1
		return $storage_array;
456
	}
457
458
	/**
459
	 * Get array of new storage key-values matching key patterns
460
	 *
461
	 * @param  Datastore_Interface $datastore
462
	 * @param  array $storage_key_patterns
463
	 * @return array
464
	 */
465 1
	public function get_storage_array( Datastore_Interface $datastore, $storage_key_patterns ) {
466 1
		$storage_array = $this->get_storage_array_for_datastore( $datastore );
467 1
		$matched_data = array();
468 1 View Code Duplication
		foreach ( $storage_array as $key => $value ) {
469 1
			if ( $this->key_toolset->storage_key_matches_any_pattern( $key, $storage_key_patterns ) ) {
470 1
				$matched_data[] = (object) array(
471 1
					'key' => $key,
472 1
					'value' => $value,
473
				);
474 1
			}
475 1
		}
476 1
		return $matched_data;
477
	}
478
479
	public function filter_storage_array( $storage_array, $datastore, $storage_key_patterns ) {
480
		if ( empty( $storage_array ) ) {
481
			$storage_array = $this->get_storage_array( $datastore, $storage_key_patterns );
482
		}
483
		return $storage_array;
484
	}
485
}
486