Completed
Push — test/legacy-storage-service ( dea7b6...29bb85 )
by
unknown
12:54 queued 10:29
created

Legacy_Storage_Service   C

Complexity

Total Complexity 61

Size/Duplication

Total Lines 430
Duplicated Lines 3.95 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 40.61%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
dl 17
loc 430
ccs 93
cts 229
cp 0.4061
rs 6.018
c 3
b 1
f 0
wmc 61
lcom 1
cbo 4

15 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
B get_field_group_permutations() 0 28 5
C get_legacy_storage_array_from_database() 0 71 14
A get_legacy_storage_array() 0 12 3
A get_value_for_legacy_key() 0 16 4
A legacy_storage_rows_to_row_descriptors() 0 14 3
C legacy_storage_row_to_row_descriptor() 0 40 8
B row_descriptor_to_storage_array() 9 55 8
A get_storage_array_for_datastore() 0 17 3
A get_storage_array() 8 17 4
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 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, 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
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
12
13
/*
14
 * Service which provides the ability to do meta queries for multi-value fields and nested fields
15
 */
16
class Legacy_Storage_Service extends Service {
17
18
	/**
19
	 * Contaier repository to fetch fields from
20
	 * 
21
	 * @var ContainerRepository
22
	 */
23
	protected $container_repository;
24
25
	/**
26
	 * Key Toolset for key generation and comparison utilities
27
	 * 
28
	 * @var Key_Toolset
29
	 */
30
	protected $key_toolset;
31
32
	/**
33
	 * List of special key suffixes that the Map field uses so save extra data
34
	 * 
35
	 * @var array
36
	 */
37
	protected $map_keys = array( 'lat', 'lng', 'zoom', 'address' );
38
39
	/**
40
	 * Cache of converted storage arrays
41
	 * 
42
	 * @var array
43
	 */
44
	protected $storage_array_cache = array();
45
46
	/**
47
	 * Service constructor
48
	 * 
49
	 * @param ContainerRepository $container_repository
50
	 */
51 1
	public function __construct( ContainerRepository $container_repository, Key_Toolset $key_toolset ) {
52 1
		$this->container_repository = $container_repository;
53 1
		$this->key_toolset = $key_toolset;
54 1
	}
55
56
	/**
57
	 * Enable the service
58
	 */
59
	protected function enabled() {
60
		add_filter( 'crb_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( 'crb_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 1
	public function is_legacy_map_key( $key ) {
77
		$map_regex = array_map( function( $map_key ) {
78 1
			return preg_quote( $map_key, '/' );
79 1
		}, $this->map_keys );
80 1
		$map_regex = '/-(' . implode( '|', $map_regex ) . ')$/';
81 1
		return 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 1
	public function get_container_for_datastore( Datastore_Interface $datastore ) {
91 1
		$containers = $this->container_repository->get_containers();
92 1
		foreach ( $containers as $container ) {
93 1
			if ( $container->get_datastore() === $datastore ) {
94 1
				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 1
	public function get_field_group_permutations( $fields ) {
107 1
		$permutations = array();
108
109 1
		foreach ( $fields as $field ) {
110 1
			if ( is_a( $field, '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 1
				$permutations[] = array(
125 1
					'field' => $field->get_base_name(),
126 1
					'group' => '',
127 1
					'children' => array(),
128
				);
129
			}
130 1
		}
131
132 1
		return $permutations;
133
	}
134
135
	/**
136
	 * Get a key-value array of legacy values for fields in the container of the passed datastore
137
	 *
138
	 * @param Container $container
139
	 * @param Datastore_Interface $datastore
0 ignored issues
show
Bug introduced by
There is no parameter named $datastore. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
140
	 * @return array
141
	 */
142
	protected function get_legacy_storage_array_from_database( Container $container ) {
143
		global $wpdb;
144
145
		$prefix = '_';
146
		$table_name = '';
147
		$table_id_column = '';
148
		$table_key_column = '';
149
		$table_value_column = '';
150
151
		$datastore = $container->get_datastore();
152
		if ( is_a( $datastore, 'Carbon_Fields\\Datastore\\Theme_Options_Datastore' ) ) {
153
			$prefix = '';
154
			$table_name = $wpdb->options;
155
			$table_key_column = 'option_name';
156
			$table_value_column = 'option_value';
157
		} else if ( is_a( $datastore, 'Carbon_Fields\\Datastore\\Meta_Datastore' ) ) {
158
			$table_name = $datastore->get_table_name();
159
			$table_id_column = $datastore->get_table_field_name();
160
			$table_key_column = 'meta_key';
161
			$table_value_column = 'meta_value';
162
		}
163
164
		if ( $table_id_column && ! $datastore->get_id() ) {
165
			return array(); // we are in a "create" view where we do not have an id (e.g. term meta)
166
		}
167
168
		$comparisons = array();
169
		$container_fields = $container->get_fields();
170
		foreach ( $container_fields as $field ) {
171
			$field_key_pattern = $prefix . $field->get_base_name();
172
173
			if ( is_a( $field, 'Carbon_Fields\\Field\\Complex_Field' ) ) {
174
				$groups = $field->get_group_names();
175
				foreach ( $groups as $group_name ) {
176
					$underscored_group_name = ( substr( $group_name, 0, 1 ) === '_' ? $group_name : '_' . $group_name );
177
					$comparisons[] = ' `' . $table_key_column . '` LIKE "' . esc_sql( $field_key_pattern . $underscored_group_name . '-' ) . '%" ';
178
				}
179
			} else {
180
				$comparisons[] = ' `' . $table_key_column . '` = "' . esc_sql( $field_key_pattern ) . '" ';
181
182
				if ( is_a( $field, 'Carbon_Fields\\Field\\Map_Field' ) ) {
183
					foreach ( $this->map_keys as $mk ) {
184
						$comparisons[] = ' `' . $table_key_column . '` = "' . esc_sql( $field_key_pattern . '-' . $mk ) . '" ';
185
					}
186
				}
187
			}
188
		}
189
190
		if ( empty( $comparisons ) ) {
191
			return array(); // no comparisons to fetch with
192
		}
193
194
		$where_clause = ' ( ' . implode( ' OR ', $comparisons ) . ' ) ';
195
		if ( $table_id_column ) {
196
			$where_clause = ' `' . $table_id_column . '` = ' . $datastore->get_id() . ' AND ' . $where_clause;
197
		}
198
		$query = '
199
			SELECT `' . $table_key_column . '` AS `key`, `' . $table_value_column . '` AS `value`
200
			FROM `' . $table_name . '`
201
			WHERE ' . $where_clause . '
202
		';
203
204
		$raw_results = $wpdb->get_results( $query );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
205
206
		$results = array();
207
		foreach ( $raw_results as $result ) {
208
			$results[ $result->key ] = $result->value;
209
		}
210
211
		return $results;
212
	}
213
214
	/**
215
	 * Get a key-value array of CF 1.5 values for fields in the container of the passed datastore
216
	 * 
217
	 * @param  Datastore_Interface $datastore
218
	 * @return array
219
	 */
220
	public function get_legacy_storage_array( Datastore_Interface $datastore ) {
221
		$container = $this->get_container_for_datastore( $datastore );
222
		if ( ! $container ) {
223
			return array(); // unhandled datastore type or no registered containers
224
		}
225
226
		if ( ! isset( $this->storage_array_cache[ $container->id ] ) ) {
227
			$this->storage_array_cache[ $container->id ] = $this->get_legacy_storage_array_from_database( $container );
228
		}
229
230
		return $this->storage_array_cache[ $container->id ];
231
	}
232
233
	/**
234
	 * Get expanded value for key from legacy storage array
235
	 * 
236
	 * @param string $key Legacy key to fetch additional values for
237
	 * @param array $legacy_storage_array key=>value array of legacy data
238
	 * @return mixed
239
	 */
240 1
	public function get_value_for_legacy_key( $key, $legacy_storage_array ) {
241 1
		$value = isset( $legacy_storage_array[ $key ] ) ? $legacy_storage_array[ $key ] : '';
242
243 1
		$first_map_key = $this->map_keys[0];
244 1
		if ( isset( $legacy_storage_array[ $key . '-' . $first_map_key ] ) ) {
245
			$value = array(
246
				Value_Set::VALUE_PROPERTY => $value,
247
			);
248
		
249
			foreach ( $this->map_keys as $map_key ) {
250
				$value[ $map_key ] = $legacy_storage_array[ $key . '-' . $map_key ];
251
			}
252
		}
253
254 1
		return $value;
255
	}
256
257
	/**
258
	 * Convert legacy storage rows to array of row descriptors
259
	 * 
260
	 * @param array $legacy_storage_array
261
	 * @param array $field_group_permutations
262
	 * @return array
263
	 */
264 1
	public function legacy_storage_rows_to_row_descriptors( $legacy_storage_array, $field_group_permutations ) {
265 1
		$row_descriptors = array();
266
267 1
		foreach ( $legacy_storage_array as $key => $value ) {
268 1
			if ( $this->is_legacy_map_key( $key ) ) {
269
				continue; // skip legacy map keys as they are handled separately through values
270
			}
271
272 1
			$value = $this->get_value_for_legacy_key( $key, $legacy_storage_array );
273 1
			$row_descriptors[] = $this->legacy_storage_row_to_row_descriptor( $key, $value, $field_group_permutations );
274 1
		}
275
276 1
		return $row_descriptors;
277
	}
278
279
	/**
280
	 * Convert legacy storage row to row descriptor
281
	 * 
282
	 * @param  string $key
283
	 * @param  string $value
284
	 * @param  array $field_group_permutations
285
	 * @return array
286
	 */
287 1
	public function legacy_storage_row_to_row_descriptor( $key, $value, $field_group_permutations ) {
288 1
		$key_pieces = explode( '-', $key );
289 1
		$field_group_level = $field_group_permutations;
290 1
		$matched_fields = array();
291 1
		foreach ( $key_pieces as $piece ) {
292 1
			foreach ( $field_group_level as $permutation ) {
293 1
				$match_regex = '/\A_?' . preg_quote( $permutation['field'], '/' ) . '(?:_(?P<group_index>\d+))?\z/';
294 1
				if ( $permutation['group'] !== '' ) {
295
					$legacy_group_name = ( $permutation['group'] === '_' ) ? $permutation['group'] : '_' . $permutation['group'];
296
					$match_regex = '/\A_?' . preg_quote( $permutation['field'], '/' ) . '(?:_(?P<group_index>\d+))?' . preg_quote( $legacy_group_name, '/' ) . '\z/';
297
				}
298
299 1
				$matches = array();
300 1
				if ( preg_match( $match_regex, $piece, $matches ) ) {
301
					$match_data = array(
302 1
						'field' => $permutation['field'],
303 1
						'group' => $permutation['group'],
304 1
						'group_index' => ( isset( $matches['group_index'] ) ? intval( $matches['group_index'] ) : -1 ),
305 1
					);
306 1
					$field_group_level = $permutation['children'];
307
308 1
					$previous_match_index = count( $matched_fields ) - 1;
309 1
					if ( isset( $matched_fields[ $previous_match_index ] ) ) {
310
						$matched_fields[ $previous_match_index ]['group_index'] = $match_data['group_index']; // indexes are offset in CF 1.5 complex keys
311
						$match_data['group_index'] = 0;
312
					}
313 1
					$matched_fields[] = $match_data;
314 1
					break; // break so next piece foreaches the new field_group_level
315
				}
316 1
			}
317 1
		}
318
319
		return array(
320
			'key' => array(
321 1
				'_raw' => $key,
322 1
				'fields' => $matched_fields,
323 1
			),
324 1
			'value' => $value,
325 1
		);
326
	}
327
328
	/**
329
	 * Convert row descriptor to array of new storage key-values
330
	 * 
331
	 * @param  array $row_descriptor
332
	 * @return array
333
	 */
334 1
	public function row_descriptor_to_storage_array( $row_descriptor ) {
335 1
		$storage_array = array();
336
337
		$hierarchy = array_map( function( $match ) {
338 1
			return $match['field'];
339 1
		}, $row_descriptor['key']['fields'] );
340
341 1
		$hierarchy_index = array_map( function( $match ) {
342
			return $match['group_index'];
343 1
		}, array_slice( $row_descriptor['key']['fields'], 0, -1 ) ); // last index is not part of the hierarchy index
344
345 1
		$complex_parents = array_slice( $hierarchy, 0, -1 );
346 1
		foreach ( $complex_parents as $level => $complex_parent ) {
347
			$complex_parent_hierarchy_index = array_slice( $hierarchy_index, 0, $level );
348
			$complex_parent_value_index = $hierarchy_index[ $level ];
349
350
			$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 );
351
			$group_name = $row_descriptor['key']['fields'][ $level ]['group'];
352
			$storage_array[ $key ] = $group_name;
353 1
		}
354
355 1
		$unserialized_value = maybe_unserialize( $row_descriptor['value'] );
356 1
		if ( is_array( $unserialized_value ) ) {
357
			if ( isset( $unserialized_value['value'] ) ) {
358
				// value is a key=>value array - save each property separately
359 View Code Duplication
				foreach ( $unserialized_value as $value_key => $value_item ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360
					$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, $value_key );
361
					$storage_array[ $key ] = $value_item;
362
				}
363
			} else {
364
				// value is a simple array - save each value separately
365
				$i = 0;
366 View Code Duplication
				foreach ( $unserialized_value as $value_item ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
367
					$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, $i, Value_Set::VALUE_PROPERTY );
368
					$storage_array[ $key ] = $value_item;
369
					$i++;
370
				}
371
			}
372 1
		} else if ( $unserialized_value === null ) {
373
			// no value found - add a keepalive key
374
			$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, '_empty' );
375
			$storage_array[ $key ] = '';
376
		} else {
377 1
			if ( count( $hierarchy ) === 1 ) {
378
				// Add a basic key as well as simple root fields will load that instead
379 1
				$key = '_' . $hierarchy[0];
380 1
				$storage_array[ $key ] = $row_descriptor['value'];
381 1
			}
382
383 1
			$key = $this->key_toolset->get_storage_key( false, $hierarchy, $hierarchy_index, 0, Value_Set::VALUE_PROPERTY );
384 1
			$storage_array[ $key ] = $row_descriptor['value'];
385
		}
386
387 1
		return $storage_array;
388
	}
389
390
	/**
391
	 * Get all data saved for a datastore in the new key-value format
392
	 * 
393
	 * @param  Datastore_Interface $datastore
394
	 * @return array
395
	 */
396 1
	public function get_storage_array_for_datastore( Datastore_Interface $datastore ) {
397 1
		$legacy_storage_array = $this->get_legacy_storage_array( $datastore );
398 1
		if ( empty( $legacy_storage_array ) ) {
399
			return array(); // no migration data found
400
		}
401
402 1
		$container = $this->get_container_for_datastore( $datastore );
403 1
		$field_group_permutations = $this->get_field_group_permutations( $container->get_fields() );
404 1
		$row_descriptors = $this->legacy_storage_rows_to_row_descriptors( $legacy_storage_array, $field_group_permutations );
405
		
406 1
		$storage_array = array();
407 1
		foreach ( $row_descriptors as $row_descriptor ) {
408 1
			$storage_array = array_merge( $storage_array, $this->row_descriptor_to_storage_array( $row_descriptor ) );
409 1
		}
410
411 1
		return $storage_array;
412
	}
413
414
	/**
415
	 * Get array of new storage key-values matching key patterns
416
	 * 
417
	 * @param  Datastore_Interface $datastore
418
	 * @param  array $storage_key_patterns
419
	 * @return array
420
	 */
421
	public function get_storage_array( Datastore_Interface $datastore, $storage_key_patterns ) {
422
		if ( ! $this->is_enabled() ) {
423
			return array();
424
		}
425
426
		$storage_array = $this->get_storage_array_for_datastore( $datastore );
427
		$matched_data = array();
428 View Code Duplication
		foreach ( $storage_array as $key => $value ) {
429
			if ( $this->key_toolset->storage_key_matches_any_pattern( $key, $storage_key_patterns ) ) {
430
				$matched_data[] = (object) array(
431
					'key' => $key,
432
					'value' => $value,
433
				);
434
			}
435
		}
436
		return $matched_data;
437
	}
438
439
	public function filter_storage_array( $storage_array, $datastore, $storage_key_patterns ) {
440
		if ( empty( $storage_array ) ) {
441
			$storage_array = $this->get_storage_array( $datastore, $storage_key_patterns );
442
		}
443
		return $storage_array;
444
	}
445
}