Completed
Push — milestone/2_0/react-ui ( 73b2ee...16415b )
by
unknown
27:57 queued 10:59
created

Fulfillable_Collection   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 359
ccs 30
cts 30
cp 1
rs 8.295
c 1
b 0
f 0
wmc 42
lcom 1
cbo 4

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A get_fulfillables() 0 3 1
A get_condition_type_list() 0 3 1
A set_condition_type_list() 0 6 1
A is_condition_type_list_whitelist() 0 3 1
A is_condition_type_allowed() 0 7 2
A propagate_condition_type_list() 0 9 3
A and_when() 0 4 1
A or_when() 0 4 1
B when() 0 25 5
A when_array() 0 5 1
A when_collection() 0 7 1
A add_fulfillable() 0 10 2
A remove_fulfillable() 0 12 3
B filter() 0 27 5
B evaluate() 0 28 6
C is_fulfilled() 0 29 7

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Carbon_Fields\Container\Fulfillable;
4
5
use Carbon_Fields\App;
6
use Carbon_Fields\Container\Condition\Factory;
7
use Carbon_Fields\Container\Fulfillable\Translator\Array_Translator;
8
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
9
10
class Fulfillable_Collection implements Fulfillable {
11
12
	/**
13
	 * Condition factory used to translated condition types
14
	 * 
15
	 * @var Factory
16
	 */
17
	protected $condition_factory;
18
19
	/**
20
	 * Array translator used to support array representations of fulfillables
21
	 * 
22
	 * @var Array_Translator
23
	 */
24
	protected $array_translator;
25
26
	/**
27
	 * Array of fulfillables in this collection
28
	 * 
29
	 * @var array<array>
30
	 */
31
	protected $fulfillables = array();
32
33
	/**
34
	 * Array of supported fulfillable comparisons
35
	 * 
36
	 * @var array<string>
37
	 */
38
	protected $supported_fulfillable_comparisons = array( 'AND', 'OR' );
39
40
	/**
41
	 * Array of allowed condition types which propagate to child collections
42
	 * 
43
	 * @var array<string>
44
	 */
45
	protected $condition_type_list = array();
46
47
	/**
48
	 * Whether the condition type list is a whitelist or a blacklist
49
	 * 
50
	 * @var bool
51
	 */
52
	protected $condition_type_list_whitelist = false;
53
54
	/**
55
	 * Constructor
56
	 * 
57
	 * @param Factory $condition_factory
58
	 * @param Array_Translator $array_translator
59
	 */
60
	public function __construct( Factory $condition_factory, Array_Translator $array_translator ) {
61
		$this->condition_factory = $condition_factory;
62
		$this->array_translator = $array_translator;
63
	}
64
65
	/**
66
	 * Get an array of the fulfillables in this collection
67
	 * 
68
	 * @return array<Fulfillable>
69
	 */
70
	public function get_fulfillables() {
71
		return $this->fulfillables;
72
	}
73
74
	/**
75
	 * Get array of allowed condition types
76
	 * 
77
	 * @return array<string>
78
	 */
79
	public function get_condition_type_list() {
80
		return $this->condition_type_list;
81
	}
82
83
	/**
84
	 * Set array of allowed condition types
85
	 * WARNING: this will NOT remove already added conditions which are no longer allowed
86
	 * 
87
	 * @param  array<string>          $condition_type_list
88
	 * @param  bool                   $whitelist
89
	 * @return Fulfillable_Collection $this
90
	 */
91
	public function set_condition_type_list( $condition_type_list, $whitelist ) {
92
		$this->condition_type_list_whitelist = $whitelist;
93
		$this->condition_type_list = $condition_type_list;
94
		$this->propagate_condition_type_list();
95
		return $this;
96
	}
97
98
	/**
99
	 * Check if conditions types list is a whitelist
100
	 * 
101
	 * @return bool
102
	 */
103
	public function is_condition_type_list_whitelist() {
104
		return $this->condition_type_list_whitelist;
105
	}
106
107
	/**
108
	 * Check if condition type is allowed
109
	 *
110
	 * @param  string $condition_type
111
	 * @return bool
112
	 */
113
	public function is_condition_type_allowed( $condition_type ) {
114
		$in_list = in_array( $condition_type, $this->get_condition_type_list() );
115
		if ( $this->is_condition_type_list_whitelist() ) {
116
			return $in_list;
117
		}
118
		return ! $in_list;
119
	}
120
121
	/**
122
	 * Propagate allowed condition types to child collections
123
	 */
124
	protected function propagate_condition_type_list() {
125
		$condition_type_list = $this->get_condition_type_list();
126
		$fulfillables = $this->get_fulfillables();
127
		foreach ( $fulfillables as $fulfillable ) {
128
			if ( is_a( $fulfillable['fulfillable'], get_class() ) ) {
129
				$fulfillable->set_condition_type_list( $condition_type_list, $this->is_condition_type_list_whitelist() );
130
			}
131
		}
132
	}
133
	
134
	/**
135
	 * Shorthand for when with AND comparison
136
	 * 
137
	 * @param  string|array|callable  $condition_type
138
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
139
	 * @param  mixed                  $value
140
	 * @return Fulfillable_Collection $this
141
	 */
142
	public function and_when( $condition_type, $comparison_operator = '=', $value = null ) {
143
		$this->when( $condition_type, $comparison_operator, $value, 'AND' );
144
		return $this;
145
	}
146
	
147
	/**
148
	 * Shorthand for when with OR comparison
149
	 * 
150
	 * @param  string|array|callable  $condition_type
151
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
152
	 * @param  mixed                  $value
153
	 * @return Fulfillable_Collection $this
154
	 */
155
	public function or_when( $condition_type, $comparison_operator = '=', $value = null ) {
156
		$this->when( $condition_type, $comparison_operator, $value, 'OR' );
157
		return $this;
158
	}
159
160
	/**
161
	 * Add fulfillable with optional comparison_operator
162
	 * This method assumes there is no fulfillable that can be compared with literal NULL
163
	 * 
164
	 * @param  string|array|callable  $condition_type
165
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
166
	 * @param  mixed                  $value
167
	 * @param  string                 $fulfillable_comparison
168
	 * @return Fulfillable_Collection $this
169
	 */
170 7
	public function when( $condition_type, $comparison_operator = '=', $value = null, $fulfillable_comparison = 'AND' ) {
171
		if ( is_array( $condition_type ) ) {
172
			return $this->when_array( $condition_type, $fulfillable_comparison );
173
		}
174
175
		if ( is_callable( $condition_type ) ) {
176
			return $this->when_collection( $condition_type, $fulfillable_comparison );
177
		}
178
179
		if ( ! $this->is_condition_type_allowed( $condition_type ) ) {
180
			Incorrect_Syntax_Exception::raise( 'Unsupported container condition used: ' . $condition_type );
181
		}
182
183 7
		if ( $value === null ) {
184
			// We do not have a supplied comparison_operator so we default to "="
185 5
			$value = $comparison_operator;
186 5
			$comparison_operator = '=';
187
		}
188
189 1
		$condition = $this->condition_factory->make( $condition_type );
190
		$condition->set_comparison_operator( $comparison_operator );
191
		$condition->set_value( $value );
192
		$this->add_fulfillable( $condition, $fulfillable_comparison );
193 6
		return $this;
194 2
	}
195
196
	/**
197
	 * Add a Fulfillable through array representation
198
	 *
199
	 * @param  array                  $fulfillable_as_array
200
	 * @param  string                 $fulfillable_comparison
201
	 * @return Fulfillable_Collection $this
202
	 */
203
	protected function when_array( $fulfillable_as_array, $fulfillable_comparison) {
204
		$fulfillable = $this->array_translator->foreign_to_fulfillable( $fulfillable_as_array );
205
		$this->add_fulfillable( $fulfillable, $fulfillable_comparison );
206
		return $this;
207
	}
208
209
	/**
210
	 * Add a Fulfillable_Collection for nested logic
211
	 *
212
	 * @param  callable               $collection_callable
213
	 * @param  string                 $fulfillable_comparison
214
	 * @return Fulfillable_Collection $this
215
	 */
216
	protected function when_collection( $collection_callable, $fulfillable_comparison) {
217
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
218
		$collection->set_condition_type_list( $this->get_condition_type_list(), $this->is_condition_type_list_whitelist() );
219
		$collection_callable( $collection );
220
		$this->add_fulfillable( $collection, $fulfillable_comparison );
221
		return $this;
222
	}
223
224
	/**
225
	 * Add fulfillable to collection
226
	 * 
227
	 * @param Fulfillable $fulfillable
228
	 * @param string      $fulfillable_comparison See static::$supported_fulfillable_comparisons
229
	 */
230 1
	public function add_fulfillable( Fulfillable $fulfillable, $fulfillable_comparison ) {
231
		if ( ! in_array( $fulfillable_comparison, $this->supported_fulfillable_comparisons ) ) {
232 1
			Incorrect_Syntax_Exception::raise( 'Invalid fulfillable comparison passed: ' . $fulfillable_comparison );
233
		}
234
235 1
		$this->fulfillables[] = array(
236
			'fulfillable_comparison' => $fulfillable_comparison,
237
			'fulfillable' => $fulfillable,
238 1
		);
239 1
	}
240
241
	/**
242
	 * Remove fulfillable from collection
243
	 * 
244
	 * @param Fulfillable $fulfillable
245
	 * @return bool Fulfillable found and removed
246
	 */
247
	public function remove_fulfillable( Fulfillable $fulfillable ) {
248
		$fulfillables = $this->get_fulfillables();
249
		foreach ( $fulfillables as $index => $fulfillable_tuple ) {
250
			if ( $fulfillable_tuple['fulfillable'] === $fulfillable ) {
251
				$fulfillables_copy = $fulfillables; // introduce a copy array to highlight array_splice mutation
252
				array_splice( $fulfillables_copy, $index, 1 );
253
				$this->fulfillables = array_values( $fulfillables_copy ); // make sure our array is indexed cleanly
254
				return true;
255
			}
256
		}
257
		return false;
258
	}
259
260
	/**
261
	 * Get a copy of the collection with conditions not in the whitelist filtered out
262
	 * 
263
	 * @param  array<string>          $condition_whitelist
264
	 * @return Fulfillable_Collection
265
	 */
266 2
	public function filter( $condition_whitelist ) {
267
		$fulfillables = $this->get_fulfillables();
268
269
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
270
		foreach ( $fulfillables as $fulfillable_tuple ) {
271
			$fulfillable = $fulfillable_tuple['fulfillable'];
272
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
273
274
			if ( is_a( $fulfillable, get_class() ) ) {
275
				$filtered_collection = $fulfillable->filter( $condition_whitelist );
276
				$filtered_collection_fulfillables = $filtered_collection->get_fulfillables();
277
				if ( empty( $filtered_collection_fulfillables ) ) {
278
					continue; // skip empty collections to reduce clutter
279
				}
280
				$collection->add_fulfillable( $filtered_collection, $fulfillable_comparison );
281
			} else {
282
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
283
				if ( ! in_array( $type, $condition_whitelist ) ) {
284 1
					continue;
285
				}
286
287 2
				$fulfillable_clone = clone $fulfillable;
288
				$collection->add_fulfillable( $fulfillable_clone, $fulfillable_comparison );
289
			}
290
		}
291 1
		return $collection;
292
	}
293
294
	/**
295
	 * Get a copy of the collection with passed conditions evaluated into boolean conditions
296
	 * Useful when evaluating only certain condition types but preserving the rest
297
	 * or when passing dynamic conditions to the front-end
298
	 * 
299
	 * @param  array<string>          $condition_types
300
	 * @param  array|boolean          $environment Environment array or a boolean value to force on conditions
301
	 * @param  array                  $comparison_operators Array of comparison operators to evaluate regardless of condition type
302
	 * @return Fulfillable_Collection
303
	 */
304 3
	public function evaluate( $condition_types, $environment, $comparison_operators = array() ) {
305
		$fulfillables = $this->get_fulfillables();
306
307
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
308
		foreach ( $fulfillables as $fulfillable_tuple ) {
309
			$fulfillable = $fulfillable_tuple['fulfillable'];
310
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
311
312
			if ( is_a( $fulfillable, get_class() ) ) {
313
				$evaluated_collection = $fulfillable->evaluate( $condition_types, $environment, $comparison_operators );
314
				$collection->add_fulfillable( $evaluated_collection, $fulfillable_comparison );
315
			} else {
316
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
317
				$comparison_operator = $fulfillable->get_comparison_operator();
318
				if ( in_array( $type, $condition_types ) || in_array( $comparison_operator, $comparison_operators ) ) {
319
					$boolean_condition = App::resolve( 'container_condition_type_boolean' );
320
					$boolean_condition->set_comparison_operator( '=' );
321
322
					$value = is_bool( $environment ) ? $environment : $fulfillable->is_fulfilled( $environment );
323
					$boolean_condition->set_value( $value );
324
					$collection->add_fulfillable( $boolean_condition, $fulfillable_comparison );
325
				} else {
326
					$collection->add_fulfillable( clone $fulfillable, $fulfillable_comparison );
327 3
				}
328
			}
329
		}
330 3
		return $collection;
331 3
	}
332
	
333
	/**
334
	 * Check if all fulfillables are fulfilled taking into account their fulfillable comparison
335
	 * 
336
	 * @param  array $environment
337
	 * @return bool
338
	 */
339 9
	public function is_fulfilled( $environment ) {
340 9
		$fulfilled = true; // return true for empty collections
341
		$fulfillables = $this->get_fulfillables();
342
343
		foreach ( $fulfillables as $i => $fulfillable_tuple ) {
344 5
			$fulfillable = $fulfillable_tuple['fulfillable'];
345 5
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
346
347 5
			if ( $i === 0 ) {
348
				// Ignore first comparison as we need a base fulfillment value
349
				$fulfilled = $fulfillable->is_fulfilled( $environment );
350 8
				continue;
351
			}
352
353
			// minor optimization - avoid unnecessary AND check if $fulfilled is currently false
354
			// false && whatever is always false
355 4
			if ( $fulfillable_comparison === 'AND' && $fulfilled ) {
356
				$fulfilled = $fulfillable->is_fulfilled( $environment );
357
			}
358
359
			// minor optimization - avoid unnecessary OR check if $fulfilled is currently true
360
			// true || whatever is always true
361 4
			if ( $fulfillable_comparison === 'OR' && ! $fulfilled ) {
362
				$fulfilled = $fulfillable->is_fulfilled( $environment );
363
			}
364 1
		}
365
366 9
		return $fulfilled;
367
	}
368
}