Passed
Push — master ( 784e82...dba655 )
by Jeroen De
05:34
created

src/Param.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace ParamProcessor;
4
5
use Exception;
6
use ValueParsers\NullParser;
7
use ValueParsers\ParseException;
8
use ValueParsers\ValueParser;
9
10
/**
11
 * NOTE: as of version 1.0, this class is for internal use only!
12
 *
13
 * Parameter class, representing the "instance" of a parameter.
14
 * Holds a ParamDefinition, user provided input (name & value) and processing state.
15
 *
16
 * @since 1.0
17
 *
18
 * @licence GNU GPL v2+
19
 * @author Jeroen De Dauw < [email protected] >
20
 */
21
final class Param implements IParam {
0 ignored issues
show
Deprecated Code introduced by
The interface ParamProcessor\IParam has been deprecated with message: since 1.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
22
23
	/**
24
	 * Indicates whether parameters not found in the criteria list
25
	 * should be stored in case they are not accepted. The default is false.
26
	 *
27
	 * @since 1.0
28
	 *
29
	 * @var boolean
30
	 */
31
	public static $accumulateParameterErrors = false;
32
33
	/**
34
	 * The original parameter name as provided by the user. This can be the
35
	 * main name or an alias.
36
	 *
37
	 * @since 1.0
38
	 *
39
	 * @var string
40
	 */
41
	protected $originalName;
42
43
	/**
44
	 * The original value as provided by the user. This is mainly retained for
45
	 * usage in error messages when the parameter turns out to be invalid.
46
	 *
47
	 * @since 1.0
48
	 *
49
	 * @var string
50
	 */
51
	protected $originalValue;
52
53
	/**
54
	 * The value of the parameter.
55
	 *
56
	 * @since 1.0
57
	 *
58
	 * @var mixed
59
	 */
60
	protected $value;
61
62
	/**
63
	 * Keeps track of how many times the parameter has been set by the user.
64
	 * This is used to detect overrides and for figuring out a parameter is missing.
65
	 *
66
	 * @since 1.0
67
	 *
68
	 * @var integer
69
	 */
70
	protected $setCount = 0;
71
72
	/**
73
	 * List of validation errors for this parameter.
74
	 *
75
	 * @since 1.0
76
	 *
77
	 * @var ProcessingError[]
78
	 */
79
	protected $errors = [];
80
81
	/**
82
	 * Indicates if the parameter was set to it's default.
83
	 *
84
	 * @since 1.0
85
	 *
86
	 * @var boolean
87
	 */
88
	protected $defaulted = false;
89
90
	/**
91
	 * @since 1.0
92
	 *
93
	 * @var ParamDefinition
94
	 */
95
	protected $definition;
96
97
	/**
98
	 * Constructor.
99
	 *
100
	 * @since 1.0
101
	 *
102
	 * @param IParamDefinition $definition
103
	 */
104 65
	public function __construct( IParamDefinition $definition ) {
105 65
		$this->definition = $definition;
106 65
	}
107
108
	/**
109
	 * Sets and cleans the original value and name.
110
	 * @see IParam::setUserValue
111
	 *
112
	 * @since 1.0
113
	 *
114
	 * @param string $paramName
115
	 * @param string $paramValue
116
	 * @param Options $options
117
	 *
118
	 * @return boolean
119
	 */
120 64
	public function setUserValue( $paramName, $paramValue, Options $options ) {
121 64
		if ( $this->setCount > 0 && !$options->acceptOverriding() ) {
122
			// TODO
123
			return false;
124
		}
125
		else {
126 64
			$this->originalName = $paramName;
127 64
			$this->originalValue = $paramValue;
128
129 64
			$this->cleanValue( $options );
130
131 64
			$this->setCount++;
132
133 64
			return true;
134
		}
135
	}
136
137
	/**
138
	 * @since 1.0
139
	 *
140
	 * @param mixed $value
141
	 */
142 57
	public function setValue( $value ) {
143 57
		$this->value = $value;
144 57
	}
145
146
	/**
147
	 * Sets the $value to a cleaned value of $originalValue.
148
	 *
149
	 * @since 1.0
150
	 *
151
	 * @param Options $options
152
	 */
153 64
	protected function cleanValue( Options $options ) {
154 64
		if ( $this->definition->isList() ) {
155
			$this->value = explode( $this->definition->getDelimiter(), $this->originalValue );
156
		}
157
		else {
158 64
			$this->value = $this->originalValue;
159
		}
160
161 64
		if ( $this->shouldTrim( $options ) ) {
162 64
			$this->trimValue();
163
		}
164
165 64
		if ( $this->shouldLowercase( $options ) ) {
166
			$this->lowercaseValue();
167
		}
168 64
	}
169
170 64
	private function shouldTrim( Options $options ): bool {
171 64
		$trim = $this->definition->trimDuringClean();
172
173 64
		if ( $trim === true ) {
174
			return true;
175
		}
176
177 64
		return is_null( $trim ) && $options->trimValues();
178
	}
179
180 64
	private function trimValue() {
181 64
		if ( is_string( $this->value ) ) {
182 64
			$this->value = trim( $this->value );
183
		}
184 54
		elseif ( $this->definition->isList() ) {
185
			foreach ( $this->value as &$element ) {
186
				if ( is_string( $element ) ) {
187
					$element = trim( $element );
188
				}
189
			}
190
		}
191 64
	}
192
193 64
	private function shouldLowercase( Options $options ): bool {
194 64
		if ( $options->lowercaseValues() ) {
195
			return true;
196
		}
197
198 64
		$definitionOptions = $this->definition->getOptions();
199
200 64
		return array_key_exists( 'tolower', $definitionOptions ) && $definitionOptions['tolower'];
201
	}
202
203
	private function lowercaseValue() {
204
		if ( $this->definition->isList() ) {
205
			foreach ( $this->value as &$element ) {
206
				if ( is_string( $element ) ) {
207
					$element = strtolower( $element );
208
				}
209
			}
210
		}
211
		elseif ( is_string( $this->value ) ) {
212
			$this->value = strtolower( $this->value );
213
		}
214
	}
215
216
	/**
217
	 * Parameter processing entry point.
218
	 * Processes the parameter. This includes parsing, validation and additional formatting.
219
	 *
220
	 * @since 1.0
221
	 *
222
	 * @param $definitions array of IParamDefinition
223
	 * @param $params array of IParam
224
	 * @param Options $options
225
	 *
226
	 * @throws Exception
227
	 */
228 65
	public function process( array &$definitions, array $params, Options $options ) {
229 65
		if ( $this->setCount == 0 ) {
230 1
			if ( $this->definition->isRequired() ) {
231
				// This should not occur, so throw an exception.
232
				throw new Exception( 'Attempted to validate a required parameter without first setting a value.' );
233
			}
234
			else {
235 1
				$this->setToDefault();
236
			}
237
		}
238
		else {
239 64
			$this->parseAndValidate( $options );
240
		}
241
242 65
		if ( !$this->hasFatalError() && ( $this->definition->shouldManipulateDefault() || !$this->wasSetToDefault() ) ) {
243 57
			$this->definition->format( $this, $definitions, $params );
244
		}
245 65
	}
246
247 64
	public function getValueParser( Options $options ): ValueParser {
248 64
		$parser = $this->definition->getValueParser();
249
250 64
		if ( get_class( $parser ) === NullParser::class ) {
251 64
			$parserType = $options->isStringlyTyped() ? 'string-parser' : 'typed-parser';
252
253
			// TODO: inject factory
254 64
			$parserClass = ParamDefinitionFactory::singleton()->getComponentForType( $this->definition->getType(), $parserType );
255
256 64
			if ( $parserClass !== NullParser::class ) {
257 50
				$parser = new $parserClass( new \ValueParsers\ParserOptions() );
258
			}
259
		}
260
261 64
		return $parser;
262
	}
263
264
	/**
265
	 * @since 1.0
266
	 *
267
	 * @param Options $options
268
	 */
269 64
	protected function parseAndValidate( Options $options ) {
270 64
		$parser = $this->getValueParser( $options );
271
272 64
		if ( $this->definition->isList() ) {
273
			$values = [];
274
275
			foreach ( $this->getValue() as $value ) {
276
				$parsedValue = $this->parseAndValidateValue( $parser, $value );
277
278
				if ( is_array( $parsedValue ) ) {
279
					$values[] = $parsedValue[0];
280
				}
281
			}
282
283
			$this->value = $values;
284
		}
285
		else {
286 64
			$parsedValue = $this->parseAndValidateValue( $parser, $this->getValue() );
287
288 64
			if ( is_array( $parsedValue ) ) {
289 64
				$this->value = $parsedValue[0];
290
			}
291
		}
292
293 64
		$this->setToDefaultIfNeeded();
294 64
	}
295
296
	/**
297
	 * Parses and validates the provided with with specified parser.
298
	 * The result is returned in an array on success. On fail, false is returned.
299
	 * The result is wrapped in an array since we need to be able to distinguish
300
	 * between the method returning false and the value being false.
301
	 *
302
	 * Parsing and validation errors get added to $this->errors.
303
	 *
304
	 * @since 1.0
305
	 *
306
	 * @param ValueParser $parser
307
	 * @param mixed $value
308
	 *
309
	 * @return array|bool
310
	 */
311 64
	protected function parseAndValidateValue( ValueParser $parser, $value ) {
312
		try {
313 64
			$value = $parser->parse( $value );
314
		}
315 7
		catch ( ParseException $parseException ) {
316 7
			$this->registerProcessingError( $parseException->getMessage() );
317 7
			return false;
318
		}
319
320 64
		if ( $value instanceof \DataValues\DataValue ) {
321 64
			$value = $value->getValue();
322
		}
323
324 64
		$this->validateValue( $value );
325
326 64
		return [ $value ];
327
	}
328
329 21
	protected function registerProcessingError( string $message ) {
330 21
		$this->errors[] = $this->newProcessingError( $message );
331 21
	}
332
333 21
	protected function newProcessingError( string $message ): ProcessingError {
334 21
		$severity = $this->isRequired() ? ProcessingError::SEVERITY_FATAL : ProcessingError::SEVERITY_NORMAL;
335 21
		return new ProcessingError( $message, $severity );
336
	}
337
338
	/**
339
	 * @since 1.0
340
	 *
341
	 * @param mixed $value
342
	 */
343 64
	protected function validateValue( $value ) {
344 64
		$validationCallback = $this->definition->getValidationCallback();
345
346 64
		if ( $validationCallback !== null && $validationCallback( $value ) !== true ) {
347 7
			$this->registerProcessingError( 'Validation callback failed' );
348
		}
349
		else {
350 64
			$validator = $this->definition->getValueValidator();
351 64
			if ( method_exists( $validator, 'setOptions' ) ) {
352 64
				$validator->setOptions( $this->definition->getOptions() );
353
			}
354 64
			$validationResult = $validator->validate( $value );
355
356 64
			if ( !$validationResult->isValid() ) {
357 16
				foreach ( $validationResult->getErrors() as $error ) {
358 16
					$this->registerProcessingError( $error->getText() );
359
				}
360
			}
361
		}
362 64
	}
363
364
	/**
365
	 * Sets the parameter value to the default if needed.
366
	 *
367
	 * @since 1.0
368
	 */
369 64
	protected function setToDefaultIfNeeded() {
370 64
		if ( $this->shouldSetToDefault() ) {
371 1
			$this->setToDefault();
372
		}
373 64
	}
374
375 64
	private function shouldSetToDefault(): bool {
376 64
		if ( $this->hasFatalError() ) {
377 20
			return false;
378
		}
379
380 56
		if ( $this->definition->isList() ) {
381
			return $this->errors !== [] && $this->value === [];
382
		}
383
384 56
		return $this->errors !== [];
385
	}
386
387
	/**
388
	 * Returns the original use-provided name.
389
	 *
390
	 * @since 1.0
391
	 *
392
	 * @throws Exception
393
	 * @return string
394
	 */
395
	public function getOriginalName(): string {
396
		if ( $this->setCount == 0 ) {
397
			throw new Exception( 'No user input set to the parameter yet, so the original name does not exist' );
398
		}
399
		return $this->originalName;
400
	}
401
402
	/**
403
	 * Returns the original use-provided value.
404
	 *
405
	 * @since 1.0
406
	 *
407
	 * @throws Exception
408
	 * @return mixed
409
	 */
410
	public function getOriginalValue() {
411
		if ( $this->setCount == 0 ) {
412
			throw new Exception( 'No user input set to the parameter yet, so the original value does not exist' );
413
		}
414
		return $this->originalValue;
415
	}
416
417
	/**
418
	 * Returns all validation errors that occurred so far.
419
	 *
420
	 * @since 1.0
421
	 *
422
	 * @return ProcessingError[]
423
	 */
424 50
	public function getErrors() {
425 50
		return $this->errors;
426
	}
427
428
	/**
429
	 * Sets the parameter value to the default.
430
	 *
431
	 * @since 1.0
432
	 */
433 2
	protected function setToDefault() {
434 2
		$this->defaulted = true;
435 2
		$this->value = $this->definition->getDefault();
436 2
	}
437
438
	public function wasSetToDefault(): bool {
439
		return $this->defaulted;
440
	}
441
442 65
	public function hasFatalError(): bool {
443 65
		foreach ( $this->errors as $error ) {
444 21
			if ( $error->isFatal() ) {
445 20
				return true;
446
			}
447
		}
448
449 57
		return false;
450
	}
451
452
	/**
453
	 * Returns the IParamDefinition this IParam was constructed from.
454
	 *
455
	 * @since 1.0
456
	 *
457
	 * @return IParamDefinition
458
	 */
459
	public function getDefinition() {
460
		return $this->definition;
461
	}
462
463
	/**
464
	 * Returns the parameters value.
465
	 *
466
	 * @since 1.0
467
	 *
468
	 * @return mixed
469
	 */
470 65
	public function &getValue() {
471 65
		return $this->value;
472
	}
473
474
	/**
475
	 * Returns if the parameter is required or not.
476
	 *
477
	 * @since 1.0
478
	 *
479
	 * @return boolean
480
	 */
481 21
	public function isRequired() {
482 21
		return $this->definition->isRequired();
483
	}
484
485
	/**
486
	 * Returns if the name of the parameter.
487
	 *
488
	 * @since 1.0
489
	 *
490
	 * @return string
491
	 */
492
	public function getName() {
493
		return $this->definition->getName();
494
	}
495
496
	/**
497
	 * Returns the parameter name aliases.
498
	 *
499
	 * @since 1.0
500
	 *
501
	 * @return string[]
502
	 */
503
	public function getAliases(): array {
504
		return $this->definition->getAliases();
505
	}
506
507
}