includes/libs/ParamValidator/TypeDef/EnumDef.php
<?php
namespace Wikimedia\ParamValidator\TypeDef;
use Wikimedia\Message\DataMessageValue;
use Wikimedia\Message\ListParam;
use Wikimedia\Message\ListType;
use Wikimedia\Message\MessageParam;
use Wikimedia\Message\MessageValue;
use Wikimedia\Message\ParamType;
use Wikimedia\Message\ScalarParam;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef;
/**
* Type definition for enumeration types.
*
* This class expects that PARAM_TYPE is an array of allowed values. Subclasses
* may override getEnumValues() to determine the allowed values differently.
*
* The result from validate() is one of the defined values.
*
* Failure codes:
* - 'badvalue': The value is not a recognized value. No data.
*
* Additional codes may be generated when using certain PARAM constants. See
* the constants' documentation for details.
*
* @since 1.34
* @unstable
*/
class EnumDef extends TypeDef {
/**
* (array) Associative array of deprecated values.
*
* Keys are the deprecated parameter values. Value is one of the following:
* - null: Parameter isn't actually deprecated.
* - true: Parameter is deprecated.
* - MessageValue: Parameter is deprecated, and this message (converted to a DataMessageValue)
* is used in place of the default for passing to $this->failure().
*
* Note that this does not add any values to the enumeration, it only
* documents existing values as being deprecated.
*
* Failure codes: (non-fatal)
* - 'deprecated-value': A deprecated value was encountered. No data.
*/
public const PARAM_DEPRECATED_VALUES = 'param-deprecated-values';
public function validate( $name, $value, array $settings, array $options ) {
$values = $this->getEnumValues( $name, $settings, $options );
if ( in_array( $value, $values, true ) ) {
// Set a warning if a deprecated parameter value has been passed
if ( empty( $options['is-default'] ) &&
isset( $settings[self::PARAM_DEPRECATED_VALUES][$value] )
) {
$msg = $settings[self::PARAM_DEPRECATED_VALUES][$value];
if ( $msg instanceof MessageValue ) {
$message = DataMessageValue::new(
$msg->getKey(),
$msg->getParams(),
'deprecated-value',
$msg instanceof DataMessageValue ? $msg->getData() : null
);
} else {
$message = $this->failureMessage( 'deprecated-value' );
}
$this->failure( $message, $name, $value, $settings, $options, false );
}
return $value;
}
$isMulti = isset( $options['values-list'] );
$this->failure(
$this->failureMessage( 'badvalue', [], $isMulti ? 'enummulti' : 'enumnotmulti' )
->textListParams( array_map( static function ( $v ) {
return new ScalarParam( ParamType::PLAINTEXT, $v );
}, $values ) )
->numParams( count( $values ) ),
$name, $value, $settings, $options
);
}
public function checkSettings( string $name, $settings, array $options, array $ret ): array {
$ret = parent::checkSettings( $name, $settings, $options, $ret );
$ret['allowedKeys'][] = self::PARAM_DEPRECATED_VALUES;
$dv = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
if ( !is_array( $dv ) ) {
$ret['issues'][self::PARAM_DEPRECATED_VALUES] = 'PARAM_DEPRECATED_VALUES must be an array, got '
. gettype( $dv );
} else {
$values = array_map( function ( $v ) use ( $name, $settings, $options ) {
return $this->stringifyValue( $name, $v, $settings, $options );
}, $this->getEnumValues( $name, $settings, $options ) );
foreach ( $dv as $k => $v ) {
$k = $this->stringifyValue( $name, $k, $settings, $options );
if ( !in_array( $k, $values, true ) ) {
$ret['issues'][] = "PARAM_DEPRECATED_VALUES contains \"$k\", which is not "
. 'one of the enumerated values';
} elseif ( $v instanceof MessageValue ) {
$ret['messages'][] = $v;
} elseif ( $v !== null && $v !== true ) {
$type = $v === false ? 'false' : ( is_object( $v ) ? get_class( $v ) : gettype( $v ) );
$ret['issues'][] = 'Values in PARAM_DEPRECATED_VALUES must be null, true, or MessageValue, '
. "but value for \"$k\" is $type";
}
}
}
return $ret;
}
public function getEnumValues( $name, array $settings, array $options ) {
return array_values( $settings[ParamValidator::PARAM_TYPE] );
}
public function stringifyValue( $name, $value, array $settings, array $options ) {
if ( !is_array( $value ) ) {
return parent::stringifyValue( $name, $value, $settings, $options );
}
return ParamValidator::implodeMultiValue( $value );
}
public function getParamInfo( $name, array $settings, array $options ) {
$info = parent::getParamInfo( $name, $settings, $options );
$info['type'] = $this->sortEnumValues(
$name,
$this->getEnumValues( $name, $settings, $options ),
$settings,
$options
);
if ( !empty( $settings[self::PARAM_DEPRECATED_VALUES] ) ) {
$deprecatedValues = array_intersect(
array_keys( $settings[self::PARAM_DEPRECATED_VALUES] ),
$this->getEnumValues( $name, $settings, $options )
);
if ( $deprecatedValues ) {
$deprecatedValues = $this->sortEnumValues( $name, $deprecatedValues, $settings, $options );
$info['deprecatedvalues'] = array_values( $deprecatedValues );
}
}
return $info;
}
public function getHelpInfo( $name, array $settings, array $options ) {
$info = parent::getHelpInfo( $name, $settings, $options );
$isMulti = !empty( $settings[ParamValidator::PARAM_ISMULTI] );
$values = $this->getEnumValuesForHelp( $name, $settings, $options );
$count = count( $values );
$i = array_search( '', $values, true );
if ( $i === false ) {
$valuesParam = new ListParam( ListType::COMMA, $values );
} else {
unset( $values[$i] );
$valuesParam = MessageValue::new( 'paramvalidator-help-type-enum-can-be-empty' )
->commaListParams( $values )
->numParams( count( $values ) );
}
$info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-enum' )
->params( $isMulti ? 2 : 1 )
->params( $valuesParam )
->numParams( $count );
// Suppress standard ISMULTI message, it should be incorporated into our type message.
$info[ParamValidator::PARAM_ISMULTI] = null;
return $info;
}
/**
* Sort enum values for help/param info output
*
* @param string $name Parameter name being described.
* @param string[] $values Values being sorted
* @param array $settings Parameter settings array.
* @param array $options Options array.
* @return string[]
*/
protected function sortEnumValues(
string $name, array $values, array $settings, array $options
): array {
// sort values by deprecation status and name
$flags = [];
foreach ( $values as $k => $value ) {
$flag = 0;
if ( isset( $settings[self::PARAM_DEPRECATED_VALUES][$value] ) ) {
$flag |= 1;
}
$flags[$k] = $flag;
}
array_multisort( $flags, $values, SORT_NATURAL );
return $values;
}
/**
* Return enum values formatted for the help message
*
* @param string $name Parameter name being described.
* @param array $settings Parameter settings array.
* @param array $options Options array.
* @return (MessageParam|string)[]
*/
protected function getEnumValuesForHelp( $name, array $settings, array $options ) {
$values = $this->getEnumValues( $name, $settings, $options );
$values = $this->sortEnumValues( $name, $values, $settings, $options );
// @todo Indicate deprecated values in some manner. Probably that needs
// MessageValue and/or MessageParam to have a generic ability to wrap
// values in HTML without that HTML coming out in the text format too.
return $values;
}
}