Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.73% covered (warning)
72.73%
24 / 33
69.23% covered (warning)
69.23%
9 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
TypeDef
72.73% covered (warning)
72.73%
24 / 33
69.23% covered (warning)
69.23%
9 / 13
26.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 supportsArrays
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 failIfNotString
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 fatal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 failure
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 failureMessage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validate
n/a
0 / 0
n/a
0 / 0
0
 normalizeSettings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkSettings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEnumValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stringifyValue
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getParamInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHelpInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Wikimedia\ParamValidator;
4
5use Wikimedia\Message\DataMessageValue;
6use Wikimedia\Message\MessageValue;
7
8/**
9 * Base definition for ParamValidator types.
10 *
11 * Most methods in this class accept an "options array". This is just the `$options`
12 * passed to ParamValidator::getValue(), ParamValidator::validateValue(), and the like
13 * and is intended for communication of non-global state to the Callbacks.
14 *
15 * @since 1.34
16 * @unstable for use in extensions. Intended to become stable to extend, at
17 *           least for use in MediaWiki, which already defines some subclasses.
18 */
19abstract class TypeDef {
20
21    /**
22     * @unstable Temporarily log warnings to detect misbehaving clients (T305973)
23     */
24    public const OPT_LOG_BAD_TYPES = 'log-bad-types';
25
26    /**
27     * Option that instructs TypeDefs to enforce the native type of parameter
28     * values, instead of allowing string values as input. This is intended for
29     * use with values coming from a JSON request body, and may accommodate for
30     * differences between the type system of PHP and JSON.
31     */
32    public const OPT_ENFORCE_JSON_TYPES = 'enforce-json-types';
33
34    /** @var Callbacks */
35    protected $callbacks;
36
37    /**
38     * @stable to call
39     *
40     * @param Callbacks $callbacks
41     */
42    public function __construct( Callbacks $callbacks ) {
43        $this->callbacks = $callbacks;
44    }
45
46    /**
47     * Whether the value may be an array.
48     * Note that this is different from multi-value.
49     * This should only return true if each value can be an array.
50     * @since 1.41
51     * @stable to override
52     * @return bool
53     */
54    public function supportsArrays() {
55        return false;
56    }
57
58    /**
59     * Fails if $value is not a string.
60     *
61     * @param string $name Parameter name being validated.
62     * @param mixed $value Value being validated.
63     * @param array $settings Parameter settings array.
64     * @param array $options Options array.
65     *
66     * @return void
67     */
68    protected function failIfNotString(
69        string $name,
70        $value,
71        array $settings,
72        array $options
73    ): void {
74        if ( !is_string( $value ) ) {
75            $this->fatal(
76                $this->failureMessage( 'needstring' )
77                    ->params( gettype( $value ) ),
78                $name, $value, $settings, $options
79            );
80        }
81    }
82
83    /**
84     * Throw a ValidationException.
85     * This is a wrapper for failure() which explicitly declares that it
86     * never returns, which is useful to static analysis tools like Phan.
87     *
88     * Note that parameters for `$name` and `$value` are always added as `$1`
89     * and `$2`.
90     *
91     * @param DataMessageValue|string $failure Failure code or message.
92     * @param string $name Parameter name being validated.
93     * @param mixed $value Value being validated.
94     * @param array $settings Parameter settings array.
95     * @param array $options Options array.
96     * @return never
97     * @throws ValidationException always
98     */
99    protected function fatal(
100        $failure, $name, $value, array $settings, array $options
101    ): never {
102        $this->failure( $failure, $name, $value, $settings, $options );
103    }
104
105    /**
106     * Record a failure message
107     *
108     * Depending on `$fatal`, this will either throw a ValidationException or
109     * call $this->callbacks->recordCondition().
110     *
111     * Note that parameters for `$name` and `$value` are always added as `$1`
112     * and `$2`.
113     *
114     * @param DataMessageValue|string $failure Failure code or message.
115     * @param string $name Parameter name being validated.
116     * @param mixed $value Value being validated.
117     * @param array $settings Parameter settings array.
118     * @param array $options Options array.
119     * @param bool $fatal Whether the failure is fatal
120     */
121    protected function failure(
122        $failure, $name, $value, array $settings, array $options, $fatal = true
123    ) {
124        if ( !is_string( $value ) ) {
125            $value = (string)$this->stringifyValue( $name, $value, $settings, $options );
126        }
127
128        if ( is_string( $failure ) ) {
129            $mv = $this->failureMessage( $failure )
130                ->plaintextParams( $name, $value );
131        } else {
132            $mv = DataMessageValue::new( $failure->getKey(), [], $failure->getCode(), $failure->getData() )
133                ->plaintextParams( $name, $value )
134                ->params( ...$failure->getParams() );
135        }
136
137        if ( $fatal ) {
138            throw new ValidationException( $mv, $name, $value, $settings );
139        }
140        $this->callbacks->recordCondition( $mv, $name, $value, $settings, $options );
141    }
142
143    /**
144     * Create a DataMessageValue representing a failure
145     *
146     * The message key will be "paramvalidator-$code" or "paramvalidator-$code-$suffix".
147     *
148     * Use DataMessageValue's param mutators to add additional MessageParams.
149     * Note that `failure()` will prepend parameters for `$name` and `$value`.
150     *
151     * @param string $code Failure code.
152     * @param array|null $data Failure data.
153     * @param string|null $suffix Suffix to append when producing the message key
154     * @return DataMessageValue
155     */
156    protected function failureMessage( $code, ?array $data = null, $suffix = null ): DataMessageValue {
157        return DataMessageValue::new(
158            "paramvalidator-$code" . ( $suffix !== null ? "-$suffix" : '' ),
159            [], $code, $data
160        );
161    }
162
163    /**
164     * Get the value from the request
165     * @stable to override
166     *
167     * @note Only override this if you need to use something other than
168     *  $this->callbacks->getValue() to fetch the value. Reformatting from a
169     *  string should typically be done by self::validate().
170     * @note Handling of ParamValidator::PARAM_DEFAULT should be left to ParamValidator,
171     *  as should PARAM_REQUIRED and the like.
172     *
173     * @param string $name Parameter name being fetched.
174     * @param array $settings Parameter settings array.
175     * @param array $options Options array.
176     * @return null|mixed Return null if the value wasn't present, otherwise a
177     *  value to be passed to self::validate().
178     */
179    public function getValue( $name, array $settings, array $options ) {
180        return $this->callbacks->getValue( $name, null, $options );
181    }
182
183    /**
184     * Validate the value
185     *
186     * When ParamValidator is processing a multi-valued parameter, this will be
187     * called once for each of the supplied values. Which may mean zero calls.
188     *
189     * When getValue() returned null, this will not be called.
190     *
191     * @param string $name Parameter name being validated.
192     * @param mixed $value Value to validate, from getValue().
193     * @param array $settings Parameter settings array.
194     * @param array $options Options array. Note the following values that may be set
195     *  by ParamValidator:
196     *   - is-default: (bool) If present and true, the value was taken from PARAM_DEFAULT rather
197     *     that being supplied by the client.
198     *   - values-list: (string[]) If defined, values of a multi-valued parameter are being processed
199     *     (and this array holds the full set of values).
200     * @return mixed Validated value
201     * @throws ValidationException if the value is invalid
202     */
203    abstract public function validate( $name, $value, array $settings, array $options );
204
205    /**
206     * Normalize a settings array
207     * @stable to override
208     * @param array $settings
209     * @return array
210     */
211    public function normalizeSettings( array $settings ) {
212        return $settings;
213    }
214
215    /**
216     * Validate a parameter settings array
217     *
218     * This is intended for validation of parameter settings during unit or
219     * integration testing, and should implement strict checks.
220     *
221     * The rest of the code should generally be more permissive.
222     *
223     * @see ParamValidator::checkSettings()
224     * @stable to override
225     *
226     * @param string $name Parameter name
227     * @param array|mixed $settings Default value or an array of settings
228     *  using PARAM_* constants.
229     * @param array $options Options array, passed through to the TypeDef and Callbacks.
230     * @param array $ret
231     *  - 'issues': (string[]) Errors detected in $settings, as English text. If the settings
232     *    are valid, this will be the empty array. Keys on input are ParamValidator constants,
233     *    allowing the typedef to easily override core validation; this need not be preserved
234     *    when returned.
235     *  - 'allowedKeys': (string[]) ParamValidator keys that are allowed in `$settings`.
236     *  - 'messages': (MessageValue[]) Messages to be checked for existence.
237     * @return array $ret, with any relevant changes.
238     */
239    public function checkSettings( string $name, $settings, array $options, array $ret ): array {
240        return $ret;
241    }
242
243    /**
244     * Get the values for enum-like parameters
245     *
246     * This is primarily intended for documentation and implementation of
247     * PARAM_ALL; it is the responsibility of the TypeDef to ensure that validate()
248     * accepts the values returned here.
249     * @stable to override
250     *
251     * @param string $name Parameter name being validated.
252     * @param array $settings Parameter settings array.
253     * @param array $options Options array.
254     * @return array|null All possible enumerated values, or null if this is
255     *  not an enumeration.
256     */
257    public function getEnumValues( $name, array $settings, array $options ) {
258        return null;
259    }
260
261    /**
262     * Convert a value to a string representation.
263     *
264     * This is intended as the inverse of getValue() and validate(): this
265     * should accept anything returned by those methods or expected to be used
266     * as PARAM_DEFAULT, and if the string from this method is passed in as client
267     * input or PARAM_DEFAULT it should give equivalent output from validate().
268     *
269     * @param string $name Parameter name being converted.
270     * @param mixed $value Parameter value being converted. Do not pass null.
271     * @param array $settings Parameter settings array.
272     * @param array $options Options array.
273     * @return string|null Return null if there is no representation of $value
274     *  reasonably satisfying the description given.
275     */
276    public function stringifyValue( $name, $value, array $settings, array $options ) {
277        if ( is_array( $value ) ) {
278            return '(array)';
279        }
280
281        return (string)$value;
282    }
283
284    /**
285     * Describe parameter settings in a machine-readable format.
286     *
287     * Keys should be short strings using lowercase ASCII letters. Values
288     * should generally be values that could be encoded in JSON or the like.
289     *
290     * This is intended to handle PARAM constants specific to this class. It
291     * generally shouldn't handle constants defined on ParamValidator itself.
292     * @stable to override
293     *
294     * @param string $name Parameter name.
295     * @param array $settings Parameter settings array.
296     * @param array $options Options array.
297     * @return array
298     */
299    public function getParamInfo( $name, array $settings, array $options ) {
300        return [];
301    }
302
303    /**
304     * Describe parameter settings in human-readable format
305     *
306     * Keys in the returned array should generally correspond to PARAM
307     * constants.
308     *
309     * If relevant, a MessageValue describing the type itself should be
310     * returned with key ParamValidator::PARAM_TYPE.
311     *
312     * The default messages for other ParamValidator-defined PARAM constants
313     * may be suppressed by returning null as the value for those constants, or
314     * replaced by returning a replacement MessageValue. Normally, however,
315     * the default messages should not be changed.
316     *
317     * MessageValues describing any other constraints applied via PARAM
318     * constants specific to this class should also be returned.
319     * @stable to override
320     *
321     * @param string $name Parameter name being described.
322     * @param array $settings Parameter settings array.
323     * @param array $options Options array.
324     * @return (MessageValue|null)[]
325     */
326    public function getHelpInfo( $name, array $settings, array $options ) {
327        return [];
328    }
329
330}