Completed
Push — milestone/2_0/container-condit... ( af96ef...c41f81 )
by
unknown
03:01
created

Fulfillable_Collection::when_array()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 2
rs 9.4285
1
<?php
2
3
namespace Carbon_Fields\Container\Condition;
4
5
use Carbon_Fields\App;
6
use Carbon_Fields\Container\Condition\Factory;
7
use Carbon_Fields\Container\Condition\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 1
	public function get_fulfillables() {
71 1
		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
		// Verify all allowed condition types exist
93
		foreach ( $condition_type_list as $condition_type ) {
94
			if ( ! $this->condition_factory->has_type( $condition_type ) ) {
95
				Incorrect_Syntax_Exception::raise( 'Unknown container condition type allowed: ' . $condition_type );
96
			}
97
		}
98
		
99
		$this->condition_type_list_whitelist = $whitelist;
100
		$this->condition_type_list = $condition_type_list;
101
		$this->propagate_condition_type_list();
102
		return $this;
103
	}
104
105
	/**
106
	 * Check if conditions types list is a whitelist
107
	 * 
108
	 * @return bool
109
	 */
110
	public function is_condition_type_list_whitelist() {
111
		return $this->condition_type_list_whitelist;
112
	}
113
114
	/**
115
	 * Check if condition type is allowed
116
	 *
117
	 * @param  string $condition_type
118
	 * @return bool
119
	 */
120
	public function is_condition_type_allowed( $condition_type ) {
121
		$in_list = in_array( $condition_type, $this->get_condition_type_list() );
122
		if ( $this->is_condition_type_list_whitelist() ) {
123
			return $in_list;
124
		}
125
		return ! $in_list;
126
	}
127
128
	/**
129
	 * Propagate allowed condition types to child collections
130
	 */
131
	protected function propagate_condition_type_list() {
132
		$condition_type_list = $this->get_condition_type_list();
133
		$fulfillables = $this->get_fulfillables();
134
		foreach ( $fulfillables as $fulfillable ) {
135
			if ( is_a( $fulfillable['fulfillable'], get_class() ) ) {
136
				$fulfillable->set_condition_type_list( $condition_type_list, $this->is_condition_type_list_whitelist() );
137
			}
138
		}
139
	}
140
	
141
	/**
142
	 * Shorthand for when with AND comparison
143
	 * 
144
	 * @param  string|array|callable  $condition_type
145
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
146
	 * @param  mixed                  $value
147
	 * @return Fulfillable_Collection $this
148
	 */
149
	public function and_when( $condition_type, $comparison_operator = '=', $value = null ) {
150
		$this->when( $condition_type, $comparison_operator, $value, 'AND' );
151
		return $this;
152
	}
153
	
154
	/**
155
	 * Shorthand for when with OR comparison
156
	 * 
157
	 * @param  string|array|callable  $condition_type
158
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
159
	 * @param  mixed                  $value
160
	 * @return Fulfillable_Collection $this
161
	 */
162
	public function or_when( $condition_type, $comparison_operator = '=', $value = null ) {
163
		$this->when( $condition_type, $comparison_operator, $value, 'OR' );
164
		return $this;
165
	}
166
167
	/**
168
	 * Add fulfillable with optional comparison_operator
169
	 * This method assumes there is no fulfillable that can be compared with literal NULL
170
	 * 
171
	 * @param  string|array|callable  $condition_type
172
	 * @param  string                 $comparison_operator Can be skipped. Defaults to "="
173
	 * @param  mixed                  $value
174
	 * @param  string                 $fulfillable_comparison
175
	 * @return Fulfillable_Collection $this
176
	 */
177 9
	public function when( $condition_type, $comparison_operator = '=', $value = null, $fulfillable_comparison = 'AND' ) {
178 9
		if ( is_array( $condition_type ) ) {
179 2
			return $this->when_array( $condition_type, $fulfillable_comparison );
180
		}
181
182 7
		if ( is_callable( $condition_type ) ) {
183 2
			return $this->when_collection( $condition_type, $fulfillable_comparison );
184
		}
185
186 7
		if ( ! $this->is_condition_type_allowed( $condition_type ) ) {
187
			Incorrect_Syntax_Exception::raise( 'Unsupported container condition used: ' . $condition_type );
188
		}
189
190 7
		if ( $value === null ) {
191
			// We do not have a supplied comparison_operator so we default to "="
192 5
			$value = $comparison_operator;
193 5
			$comparison_operator = '=';
194 5
		}
195
196 7
		$condition = $this->condition_factory->make( $condition_type );
197 6
		$condition->set_comparison_operator( $comparison_operator );
198 6
		$condition->set_value( $value );
199 6
		$this->add_fulfillable( $condition, $fulfillable_comparison );
200 6
		return $this;
201
	}
202
203
	/**
204
	 * Add a Fulfillable through array representation
205
	 *
206
	 * @param  array                  $fulfillable_as_array
207
	 * @param  string                 $fulfillable_comparison
208
	 * @return Fulfillable_Collection $this
209
	 */
210
	protected function when_array( $fulfillable_as_array, $fulfillable_comparison) {
211
		$fulfillable = $this->array_translator->foreign_to_fulfillable( $fulfillable_as_array );
212
		$this->add_fulfillable( $fulfillable, $fulfillable_comparison );
213
		return $this;
214
	}
215
216
	/**
217
	 * Add a Fulfillable_Collection for nested logic
218
	 *
219
	 * @param  callable               $collection_callable
220
	 * @param  string                 $fulfillable_comparison
221
	 * @return Fulfillable_Collection $this
222
	 */
223
	protected function when_collection( $collection_callable, $fulfillable_comparison) {
224
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
225
		$collection->set_condition_type_list( $this->get_condition_type_list(), $this->is_condition_type_list_whitelist() );
226
		$collection_callable( $collection );
227
		$this->add_fulfillable( $collection, $fulfillable_comparison );
228
		return $this;
229
	}
230
231
	/**
232
	 * Add fulfillable to collection
233
	 * 
234
	 * @param Fulfillable $fulfillable
235
	 * @param string      $fulfillable_comparison See static::$supported_fulfillable_comparisons
236
	 */
237 2
	public function add_fulfillable( Fulfillable $fulfillable, $fulfillable_comparison ) {
238 2
		if ( ! in_array( $fulfillable_comparison, $this->supported_fulfillable_comparisons ) ) {
239 1
			Incorrect_Syntax_Exception::raise( 'Invalid fulfillable comparison passed: ' . $fulfillable_comparison );
240
		}
241
242 1
		$this->fulfillables[] = array(
243 1
			'fulfillable_comparison' => $fulfillable_comparison,
244 1
			'fulfillable' => $fulfillable,
245
		);
246 1
	}
247
248
	/**
249
	 * Remove fulfillable from collection
250
	 * 
251
	 * @param Fulfillable $fulfillable
252
	 * @return bool Fulfillable found and removed
253
	 */
254
	public function remove_fulfillable( Fulfillable $fulfillable ) {
255
		$fulfillables = $this->get_fulfillables();
256
		foreach ( $fulfillables as $index => $fulfillable_tuple ) {
257
			if ( $fulfillable_tuple['fulfillable'] === $fulfillable ) {
258
				$fulfillables_copy = $this->get_fulfillables(); // introduce a copy array to highlight array_splice mutation
259
				array_splice( $fulfillables_copy, $index, 1 );
260
				$this->fulfillables = array_values( $fulfillables_copy ); // make sure our array is indexed cleanly
1 ignored issue
show
Documentation Bug introduced by
It seems like array_values($fulfillables_copy) of type array<integer,?> is incompatible with the declared type array<integer,array> of property $fulfillables.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
261
				return true;
262
			}
263
		}
264
		return false;
265
	}
266
267
	/**
268
	 * Get a copy of the collection with conditions not in the whitelist filtered out
269
	 * 
270
	 * @param  array<string>          $condition_whitelist
271
	 * @return Fulfillable_Collection
272
	 */
273 2
	public function filter( $condition_whitelist ) {
274 2
		$fulfillables = $this->get_fulfillables();
275
276 2
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
277 2
		foreach ( $fulfillables as $fulfillable_tuple ) {
278 2
			$fulfillable = $fulfillable_tuple['fulfillable'];
279 2
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
280
281 2
			if ( is_a( $fulfillable, get_class() ) ) {
282 1
				$filtered_collection = $fulfillable->filter( $condition_whitelist );
283 1
				$filtered_collection_fulfillables = $filtered_collection->get_fulfillables();
284 1
				if ( empty( $filtered_collection_fulfillables ) ) {
285 1
					continue; // skip empty collections to reduce clutter
286
				}
287
				$collection->add_fulfillable( $filtered_collection, $fulfillable_comparison );
288
			} else {
289 2
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
290 2
				if ( ! in_array( $type, $condition_whitelist ) ) {
291 2
					continue;
292
				}
293
294 2
				$fulfillable_clone = clone $fulfillable;
295 2
				$collection->add_fulfillable( $fulfillable_clone, $fulfillable_comparison );
296
			}
297 2
		}
298 2
		return $collection;
299
	}
300
301
	/**
302
	 * Get a copy of the collection with passed conditions evaluated into boolean conditions
303
	 * Useful when evaluating only certain condition types but preserving the rest
304
	 * or when passing dynamic conditions to the front-end
305
	 * 
306
	 * @param  array<string>          $condition_types
307
	 * @param  array|boolean          $environment Environment array or a boolean value to force on conditions
308
	 * @return Fulfillable_Collection
309
	 */
310
	public function evaluate( $condition_types, $environment ) {
311
		$fulfillables = $this->get_fulfillables();
312
313
		$collection = App::resolve( 'container_condition_fulfillable_collection' );
314
		foreach ( $fulfillables as $fulfillable_tuple ) {
315
			$fulfillable = $fulfillable_tuple['fulfillable'];
316
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
317
318
			if ( is_a( $fulfillable, get_class() ) ) {
319
				$evaluated_collection = $fulfillable->evaluate( $condition_types, $environment );
320
				$collection->add_fulfillable( $evaluated_collection, $fulfillable_comparison );
321
			} else {
322
				$type = $this->condition_factory->get_type( get_class( $fulfillable ) );
323
				if ( in_array( $type, $condition_types ) ) {
324
					$boolean_condition = App::resolve( 'container_condition_type_boolean' );
325
					$boolean_condition->set_comparison_operator( '=' );
326
327
					$value = is_bool( $environment ) ? $environment : $fulfillable->is_fulfilled( $environment );
328
					$boolean_condition->set_value( $value );
329
					$collection->add_fulfillable( $boolean_condition, $fulfillable_comparison );
330
				} else {
331
					$collection->add_fulfillable( clone $fulfillable, $fulfillable_comparison );
332
				}
333
			}
334
		}
335
		return $collection;
336
	}
337
	
338
	/**
339
	 * Check if all fulfillables are fulfilled taking into account their fulfillable comparison
340
	 * 
341
	 * @param  array $environment
342
	 * @return bool
343
	 */
344 14
	public function is_fulfilled( $environment ) {
345 14
		$fulfilled = true; // return true for empty collections
346 14
		$fulfillables = $this->get_fulfillables();
347
348 14
		foreach ( $fulfillables as $i => $fulfillable_tuple ) {
349 13
			$fulfillable = $fulfillable_tuple['fulfillable'];
350 13
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $fulfillable_comparison exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
351
352 13
			if ( $i === 0 ) {
353
				// Ignore first comparison as we need a base fulfillment value
354 13
				$fulfilled = $fulfillable->is_fulfilled( $environment );
355 13
				continue;
356
			}
357
358
			// minor optimization - avoid unnecessary AND check if $fulfilled is currently false
359
			// false && whatever is always false
360 4
			if ( $fulfillable_comparison === 'AND' && $fulfilled ) {
361 2
				$fulfilled = $fulfillable->is_fulfilled( $environment );
362 2
			}
363
364
			// minor optimization - avoid unnecessary OR check if $fulfilled is currently true
365
			// true || whatever is always true
366 4
			if ( $fulfillable_comparison === 'OR' && ! $fulfilled ) {
367 1
				$fulfilled = $fulfillable->is_fulfilled( $environment );
368 1
			}
369 14
		}
370
371 14
		return $fulfilled;
372
	}
373
}