Completed
Push — milestone/2_0/container-condit... ( 045f6a...a2cac7 )
by
unknown
02:59
created

Fulfillable_Collection::or_when()   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 3
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
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 Factory
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $array_translator of type object<Carbon_Fields\Con...lator\Array_Translator> is incompatible with the declared type object<Carbon_Fields\Container\Condition\Factory> of property $array_translator.

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...
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() );
1 ignored issue
show
Bug introduced by
The method set_condition_type_list cannot be called on $fulfillable (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
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 );
0 ignored issues
show
Bug introduced by
The method foreign_to_fulfillable() does not seem to exist on object<Carbon_Fields\Container\Condition\Factory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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
	 * Check if all fulfillables are fulfilled taking into account their fulfillable comparison
303
	 * 
304
	 * @param  array $environment
305
	 * @return bool
306
	 */
307 14
	public function is_fulfilled( $environment ) {
308 14
		$fulfilled = true; // return true for empty collections
309 14
		$fulfillables = $this->get_fulfillables();
310
311 14
		foreach ( $fulfillables as $i => $fulfillable_tuple ) {
312 13
			$fulfillable = $fulfillable_tuple['fulfillable'];
313 13
			$fulfillable_comparison = $fulfillable_tuple['fulfillable_comparison'];
314
315 13
			if ( $i === 0 ) {
316
				// Ignore first comparison as we need a base fulfillment value
317 13
				$fulfilled = $fulfillable->is_fulfilled( $environment );
318 13
				continue;
319
			}
320
321
			// minor optimization - avoid unnecessary AND check if $fulfilled is currently false
322
			// false && whatever is always false
323 4
			if ( $fulfillable_comparison === 'AND' && $fulfilled ) {
324 2
				$fulfilled = $fulfillable->is_fulfilled( $environment );
325 2
			}
326
327
			// minor optimization - avoid unnecessary OR check if $fulfilled is currently true
328
			// true || whatever is always true
329 4
			if ( $fulfillable_comparison === 'OR' && ! $fulfilled ) {
330 1
				$fulfilled = $fulfillable->is_fulfilled( $environment );
331 1
			}
332 14
		}
333
334 14
		return $fulfilled;
335
	}
336
}