Completed
Push — milestone/2_0/container-condit... ( 3ee472...f04f2b )
by
unknown
02:49
created

Fulfillable_Collection::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Carbon_Fields\Container\Condition\Fulfillable;
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 = $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'];
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
}