Completed
Push — master ( 15d61d...31845a )
by Jeroen De
07:37
created

src/ParamDefinition.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 ParamProcessor\PackagePrivate\Param;
7
use ValueParsers\NullParser;
8
use ValueParsers\ValueParser;
9
use ValueValidators\NullValidator;
10
use ValueValidators\ValueValidator;
11
12
/**
13
 * Specifies what kind of values are accepted, how they should be validated,
14
 * how they should be formatted, what their dependencies are and how they should be described.
15
 *
16
 * Try to avoid using this interface outside of ParamProcessor for anything else than defining parameters.
17
 * In particular, do not derive from this class to implement methods such as formatValue.
18
 *
19
 * @since 1.0
20
 *
21
 * @licence GNU GPL v2+
22
 * @author Jeroen De Dauw < [email protected] >
23
 */
24
/* final */ class ParamDefinition implements IParamDefinition {
25
26
	/**
27
	 * Indicates whether parameters that are provided more then once  should be accepted,
28
	 * and use the first provided value, or not, and generate an error.
29
	 *
30
	 * @deprected since 1.7
31
	 *
32
	 * @var boolean
33
	 */
34
	public static $acceptOverriding = false;
35
36
	/**
37
	 * Indicates whether parameters not found in the criteria list
38
	 * should be stored in case they are not accepted. The default is false.
39
	 *
40
	 * @deprected since 1.7
41
	 *
42
	 * @var boolean
43
	 */
44
	public static $accumulateParameterErrors = false;
45
46
	protected $type;
47
	protected $name;
48
	protected $default;
49
	protected $isList;
50
51
	/**
52
	 * A message that acts as description for the parameter or false when there is none.
53
	 * Can be obtained via getMessage and set via setMessage.
54
	 *
55
	 * @var string
56
	 */
57
	protected $message;
58
59
	/**
60
	 * Indicates if the parameter value should trimmed during the clean process.
61
	 * @var boolean|null
62
	 */
63
	protected $trimValue = null;
64
65
	/**
66
	 * Indicates if the parameter manipulations should be applied to the default value.
67
	 * @var boolean
68
	 */
69
	protected $applyManipulationsToDefault = true;
70
71
	/**
72
	 * Dependency list containing parameters that need to be handled before this one.
73
	 * @var string[]
74
	 */
75
	protected $dependencies = [];
76
77
	/**
78
	 * @var string
79
	 */
80
	protected $delimiter = ',';
81
82
	/**
83
	 * List of aliases for the parameter name.
84
	 *
85
	 * @var string[]
86
	 */
87
	protected $aliases = [];
88
89
	/**
90
	 * Original array definition of the parameter
91
	 * @var array
92
	 */
93
	protected $options = [];
94
95
	/**
96
	 * @var ValueParser|null
97
	 */
98
	protected $parser = null;
99
100
	/**
101
	 * @var ValueValidator|null
102
	 */
103
	protected $validator = null;
104
105
	/**
106
	 * @var callable|null
107
	 */
108
	protected $validationFunction = null;
109
110
	/**
111
	 * @param string $type
112
	 * @param string $name
113
	 * @param mixed $default Use null for no default (which makes the parameter required)
114
	 * @param string $message
115
	 * @param boolean $isList
116
	 */
117 90
	public function __construct( string $type, string $name, $default = null, string $message = null, bool $isList = false ) {
118 90
		$this->type = $type;
119 90
		$this->name = $name;
120 90
		$this->default = $default;
121 90
		$this->message = $message ?? 'validator-message-nodesc';
122 90
		$this->isList = $isList;
123
124 90
		$this->postConstruct();
125 90
	}
126
127
	/**
128
	 * Allows deriving classed to do additional stuff on instance construction
129
	 * without having to get and pass all the constructor arguments.
130
	 *
131
	 * @since 1.0
132
	 */
133 90
	protected function postConstruct() {
134
135 90
	}
136
137
	/**
138
	 * Returns if the value should be trimmed before validation and any further processing.
139
	 * - true: always trim
140
	 * - false: never trim
141
	 * - null: trim based on context settings
142
	 */
143 105
	public function trimDuringClean(): ?bool {
144 105
		return $this->trimValue;
145
	}
146
147
	/**
148
	 * Returns the parameter name aliases.
149
	 * @return string[]
150
	 */
151
	public function getAliases(): array {
152
		return $this->aliases;
153
	}
154
155
	public function hasAlias( string $alias ): bool {
156
		return in_array( $alias, $this->getAliases() );
157
	}
158
159
	/**
160
	 * @deprecated since 1.7
161
	 * Returns if the parameter has a certain dependency.
162
	 */
163
	public function hasDependency( string $dependency ): bool {
164
		return in_array( $dependency, $this->getDependencies() );
165
	}
166
167
	/**
168
	 * Returns the list of allowed values, or an empty array if there is no such restriction.
169
	 */
170
	public function getAllowedValues(): array {
171
		$allowedValues = [];
172
173
		if ( $this->validator !== null && method_exists( $this->validator, 'getWhitelistedValues' ) ) {
174
			if ( method_exists( $this->validator, 'setOptions' ) ) {
175
				$this->validator->setOptions( $this->options );
176
			}
177
178
			$allowedValues = $this->validator->getWhitelistedValues();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface ValueValidators\ValueValidator as the method getWhitelistedValues() does only exist in the following implementations of said interface: ValueValidators\DimensionValidator, ValueValidators\ListValidator, ValueValidators\RangeValidator, ValueValidators\StringValidator, ValueValidators\TitleValidator, ValueValidators\ValueValidatorObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
179
180
			if ( $allowedValues === false ) {
181
				$allowedValues = [];
182
			}
183
		}
184
185
		return $allowedValues;
186
	}
187
188
	/**
189
	 * @deprecated since 1.7
190
	 *
191
	 * @param mixed $default
192
	 * @param boolean $manipulate Should the default be manipulated or not? Since 0.4.6.
193
	 */
194
	public function setDefault( $default, $manipulate = true ) {
195
		$this->default = $default;
196
		$this->setDoManipulationOfDefault( $manipulate );
197
	}
198
199
	/**
200
	 * Returns the default value.
201
	 * @return mixed
202
	 */
203 15
	public function getDefault() {
204 15
		return $this->default;
205
	}
206
207
	/**
208
	 * Returns a message describing the parameter.
209
	 */
210 4
	public function getMessage(): string {
211 4
		return $this->message;
212
	}
213
214
	/**
215
	 * This should be a message key, ie something that can be passed
216
	 * to wfMsg. Not an actual text.
217
	 */
218
	public function setMessage( string $message ) {
219
		$this->message = $message;
220
	}
221
222
	/**
223
	 * Set if the parameter manipulations should be applied to the default value.
224
	 */
225
	public function setDoManipulationOfDefault( bool $doOrDoNotThereIsNoTry ) {
226
		$this->applyManipulationsToDefault = $doOrDoNotThereIsNoTry;
227
	}
228
229 92
	public function shouldManipulateDefault(): bool {
230 92
		return $this->applyManipulationsToDefault;
231
	}
232
233
	/**
234
	 * Adds one or more aliases for the parameter name.
235
	 *
236
	 * @param string|string[] $aliases
237
	 */
238
	public function addAliases( $aliases ) {
239
		$args = func_get_args();
240
		$this->aliases = array_merge( $this->aliases, is_array( $args[0] ) ? $args[0] : $args );
241
	}
242
243
	/**
244
	 * Adds one or more dependencies. There are the names of parameters
245
	 * that need to be validated and formatted before this one.
246
	 *
247
	 * @param string|string[] $dependencies
248
	 */
249
	public function addDependencies( $dependencies ) {
250
		$args = func_get_args();
251
		$this->dependencies = array_merge( $this->dependencies, is_array( $args[0] ) ? $args[0] : $args );
252
	}
253
254 109
	public function getName(): string {
255 109
		return $this->name;
256
	}
257
258
	/**
259
	 * Returns a message key for a message describing the parameter type.
260
	 */
261
	public function getTypeMessage(): string {
262
		$message = 'validator-type-' . $this->getType();
263
264
		if ( $this->isList() ) {
265
			$message .= '-list';
266
		}
267
268
		return $message;
269
	}
270
271
	/**
272
	 * @deprecated since 1.7
273
	 * Returns a list of dependencies the parameter has, in the form of
274
	 * other parameter names.
275
	 *
276
	 * @return string[]
277
	 */
278 49
	public function getDependencies(): array {
279 49
		return $this->dependencies;
280
	}
281
282 36
	public function isRequired(): bool {
283 36
		return is_null( $this->default );
284
	}
285
286 105
	public function isList(): bool {
287 105
		return $this->isList;
288
	}
289
290
	/**
291
	 * Returns the delimiter to use to split the raw value in case the
292
	 * parameter is a list.
293
	 */
294
	public function getDelimiter(): string {
295
		return $this->delimiter;
296
	}
297
298
	/**
299
	 * Sets the delimiter to use to split the raw value in case the
300
	 * parameter is a list.
301
	 */
302
	public function setDelimiter( string $delimiter ) {
303
		$this->delimiter = $delimiter;
304
	}
305
306
	/**
307
	 * Sets the parameter definition values contained in the provided array.
308
	 *
309
	 * @deprecated since 1.7
310
	 * TODO: provide alternative in ParamDefinitionFactory
311
	 */
312 85
	public function setArrayValues( array $param ) {
313 85
		if ( array_key_exists( 'aliases', $param ) ) {
314
			$this->addAliases( $param['aliases'] );
315
		}
316
317 85
		if ( array_key_exists( 'dependencies', $param ) ) {
318
			$this->addDependencies( $param['dependencies'] );
319
		}
320
321 85
		if ( array_key_exists( 'trim', $param ) ) {
322
			$this->trimValue = $param['trim'];
323
		}
324
325 85
		if ( array_key_exists( 'delimiter', $param ) ) {
326
			$this->delimiter = $param['delimiter'];
327
		}
328
329 85
		if ( array_key_exists( 'manipulatedefault', $param ) ) {
330
			$this->setDoManipulationOfDefault( $param['manipulatedefault'] );
331
		}
332
333 85
		$this->options = $param;
334 85
	}
335
336
	/**
337
	 * @see IParamDefinition::format
338
	 *
339
	 * @deprecated
340
	 *
341
	 * @param IParam $param
342
	 * @param IParamDefinition[] $definitions
343
	 * @param IParam[] $params
344
	 */
345 92
	public function format( IParam $param, array &$definitions, array $params ) {
346
		/**
347
		 * @var Param $param
348
		 */
349
350 92
		if ( $this->isList() && is_array( $param->getValue() ) ) {
351
			// TODO: if isList returns true, the value should be an array.
352
			// The second check here is to avoid a mysterious error.
353
			// Should have logging that writes down the value whenever this occurs.
354
355
			$values = $param->getValue();
356
357
			foreach ( $values as &$value ) {
358
				$value = $this->formatValue( $value, $param, $definitions, $params );
359
			}
360
361
			$param->setValue( $values );
362
			$this->formatList( $param, $definitions, $params );
363
		}
364
		else {
365 92
			$param->setValue( $this->formatValue( $param->getValue(), $param, $definitions, $params ) );
366
		}
367
368
		// deprecated, deriving classes should not add array-definitions to the list
369 92
		$definitions = self::getCleanDefinitions( $definitions );
370
371 92
		if ( array_key_exists( 'post-format', $this->options ) ) {
372
			$param->setValue( call_user_func( $this->options['post-format'], $param->getValue() ) );
373
		}
374 92
	}
375
376
	/**
377
	 * Formats the parameters values to their final result.
378
	 *
379
	 * @since 1.0
380
	 * @deprecated
381
	 *
382
	 * @param $param IParam
383
	 * @param $definitions array of IParamDefinition
384
	 * @param $params array of IParam
385
	 */
386
	protected function formatList( IParam $param, array &$definitions, array $params ) {
387
	}
388
389
	/**
390
	 * Formats the parameter value to it's final result.
391
	 *
392
	 * @since 1.0
393
	 * @deprecated
394
	 *
395
	 * @param mixed $value
396
	 * @param IParam $param
397
	 * @param IParamDefinition[] $definitions
398
	 * @param IParam[] $params
399
	 *
400
	 * @return mixed
401
	 */
402 88
	protected function formatValue( $value, IParam $param, array &$definitions, array $params ) {
403 88
		return $value;
404
	}
405
406
	/**
407
	 * Returns a cleaned version of the list of parameter definitions.
408
	 * This includes having converted all supported definition types to
409
	 * ParamDefinition classes and having all keys set to the names of the
410
	 * corresponding parameters.
411
	 *
412
	 * @deprecated since 1.7 - use ParamDefinitionFactory
413
	 *
414
	 * @param ParamDefinition[] $definitions
415
	 *
416
	 * @return ParamDefinition[]
417
	 * @throws Exception
418
	 */
419 96
	public static function getCleanDefinitions( array $definitions ): array {
420 96
		return ParamDefinitionFactory::singleton()->newDefinitionsFromArrays( $definitions );
421
	}
422
423
	/**
424
	 * Returns an identifier for the type of the parameter.
425
	 */
426 116
	public function getType(): string {
427 116
		return $this->type;
428
	}
429
430
	/**
431
	 * Returns a ValueParser object to parse the parameters value.
432
	 */
433 104
	public function getValueParser(): ValueParser {
434 104
		if ( $this->parser === null ) {
435 104
			$this->parser = new NullParser();
436
		}
437
438 104
		return $this->parser;
439
	}
440
441
	/**
442
	 * Returns a ValueValidator that can be used to validate the parameters value.
443
	 */
444 99
	public function getValueValidator(): ValueValidator {
445 99
		if ( $this->validator === null ) {
446 2
			$this->validator = new NullValidator();
447
		}
448
449 99
		return $this->validator;
450
	}
451
452
	public function setValueParser( ValueParser $parser ) {
453
		$this->parser = $parser;
454
	}
455
456 85
	public function setValueValidator( ValueValidator $validator ) {
457 85
		$this->validator = $validator;
458 85
	}
459
460
	/**
461
	 * Sets a validation function that will be run before the ValueValidator.
462
	 *
463
	 * This can be used instead of a ValueValidator where validation is very
464
	 * trivial, ie checking if something is a boolean can be done with is_bool.
465
	 */
466 44
	public function setValidationCallback( ?callable $validationFunction ) {
467 44
		$this->validationFunction = $validationFunction;
468 44
	}
469
470
	/**
471
	 * @see setValidationCallback
472
	 */
473 99
	public function getValidationCallback(): ?callable {
474 99
		return $this->validationFunction;
475
	}
476
477 105
	public function getOptions(): array {
478 105
		return $this->options;
479
	}
480
481
}
482