| Current File : /home/jvzmxxx/wiki1/vendor/param-processor/param-processor/src/Processor.php |
<?php
namespace ParamProcessor;
/**
* Class for parameter validation of a single parser hook or other parametrized construct.
*
* @since 0.1
*
* @licence GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
* @author Daniel Werner
*/
class Processor {
/**
* Flag for unnamed default parameters used in Processor::setFunctionParams() to determine that
* a parameter should not have a named fallback.
*
* @since 0.4.13
*/
const PARAM_UNNAMED = 1;
/**
* Array containing the parameters.
*
* @since 0.4
*
* @var Param[]
*/
private $params;
/**
* Associative array containing parameter names (keys) and their user-provided data (values).
* This list is needed because additional parameter definitions can be added to the $parameters
* field during validation, so we can't determine in advance if a parameter is unknown.
*
* @since 0.4
*
* @var string[]
*/
private $rawParameters = [];
/**
* Array containing the names of the parameters to handle, ordered by priority.
*
* @since 0.4
*
* @var string[]
*/
private $paramsToHandle = [];
/**
*
*
* @since 1.0
*
* @var IParamDefinition[]
*/
private $paramDefinitions = [];
/**
* List of ProcessingError.
*
* @since 0.4
*
* @var ProcessingError[]
*/
private $errors = [];
/**
* Options for this validator object.
*
* @since 1.0
*
* @var Options
*/
private $options;
/**
* Constructor.
*
* @param Options $options
*
* @since 1.0
*/
public function __construct( Options $options ) {
$this->options = $options;
}
/**
* Constructs and returns a Validator object based on the default options.
*
* @since 1.0
*
* @return Processor
*/
public static function newDefault() {
return new Processor( new Options() );
}
/**
* Constructs and returns a Validator object based on the provided options.
*
* @since 1.0
*
* @param Options $options
*
* @return Processor
*/
public static function newFromOptions( Options $options ) {
return new Processor( $options );
}
/**
* Returns the options used by this Validator object.
*
* @since 1.0
*
* @return Options
*/
public function getOptions() {
return $this->options;
}
/**
* Determines the names and values of all parameters. Also takes care of default parameters.
* After that the resulting parameter list is passed to Processor::setParameters
*
* @since 0.4
*
* @param array $rawParams
* @param array $parameterInfo
* @param array $defaultParams array of strings or array of arrays to define which parameters can be used unnamed.
* The second value in array-form is reserved for flags. Currently, Processor::PARAM_UNNAMED determines that
* the parameter has no name which can be used to set it. Therefore all these parameters must be set before
* any named parameter. The effect is, that '=' within the string won't confuse the parameter anymore like
* it would happen with default parameters that still have a name as well.
*/
public function setFunctionParams( array $rawParams, array $parameterInfo, array $defaultParams = [] ) {
$parameters = [];
$nr = 0;
$defaultNr = 0;
$lastUnnamedDefaultNr = -1;
/*
* Find last parameter with self::PARAM_UNNAMED set. Tread all parameters in front as
* the flag were set for them as well to ensure that there can't be any unnamed params
* after the first named param. Wouldn't be possible to determine which unnamed value
* belongs to which parameter otherwise.
*/
for( $i = count( $defaultParams ) - 1; $i >= 0 ; $i-- ) {
$dflt = $defaultParams[$i];
if( is_array( $dflt ) && !empty( $dflt[1] ) && ( $dflt[1] | self::PARAM_UNNAMED ) ) {
$lastUnnamedDefaultNr = $i;
break;
}
}
foreach ( $rawParams as $arg ) {
// Only take into account strings. If the value is not a string,
// it is not a raw parameter, and can not be parsed correctly in all cases.
if ( is_string( $arg ) ) {
$parts = explode( '=', $arg, ( $nr <= $lastUnnamedDefaultNr ? 1 : 2 ) );
// If there is only one part, no parameter name is provided, so try default parameter assignment.
// Default parameters having self::PARAM_UNNAMED set for having no name alias go here in any case.
if ( count( $parts ) == 1 ) {
// Default parameter assignment is only possible when there are default parameters!
if ( count( $defaultParams ) > 0 ) {
$defaultParam = array_shift( $defaultParams );
if( is_array( $defaultParam ) ) {
$defaultParam = $defaultParam[0];
}
$defaultParam = strtolower( $defaultParam );
$parameters[$defaultParam] = [
'original-value' => trim( $parts[0] ),
'default' => $defaultNr,
'position' => $nr
];
$defaultNr++;
}
else {
// It might be nice to have some sort of warning or error here, as the value is simply ignored.
}
} else {
$paramName = trim( strtolower( $parts[0] ) );
$parameters[$paramName] = [
'original-value' => trim( $parts[1] ),
'default' => false,
'position' => $nr
];
// Let's not be evil, and remove the used parameter name from the default parameter list.
// This code is basically a remove array element by value algorithm.
$newDefaults = [];
foreach( $defaultParams as $defaultParam ) {
if ( $defaultParam != $paramName ) $newDefaults[] = $defaultParam;
}
$defaultParams = $newDefaults;
}
}
$nr++;
}
$this->setParameters( $parameters, $parameterInfo );
}
/**
* Loops through a list of provided parameters, resolves aliasing and stores errors
* for unknown parameters and optionally for parameter overriding.
*
* @param array $parameters Parameter name as key, parameter value as value
* @param IParamDefinition[] $paramDefinitions List of parameter definitions. Either ParamDefinition objects or equivalent arrays.
*/
public function setParameters( array $parameters, array $paramDefinitions ) {
$this->paramDefinitions = ParamDefinition::getCleanDefinitions( $paramDefinitions );
// Loop through all the user provided parameters, and distinguish between those that are allowed and those that are not.
foreach ( $parameters as $paramName => $paramData ) {
if ( $this->options->lowercaseNames() ) {
$paramName = strtolower( $paramName );
}
if ( $this->options->trimNames() ) {
$paramName = trim( $paramName );
}
$paramValue = is_array( $paramData ) ? $paramData['original-value'] : $paramData;
$this->rawParameters[$paramName] = $paramValue;
}
}
/**
* Registers an error.
*
* @since 0.4
*
* @param string $message
* @param mixed $tags string or array
* @param integer $severity
*/
private function registerNewError( $message, $tags = [], $severity = ProcessingError::SEVERITY_NORMAL ) {
$this->registerError(
new ProcessingError(
$message,
$severity,
$this->options->getName(),
(array)$tags
)
);
}
/**
* Registers an error.
*
* @since 0.4
*
* @param ProcessingError $error
*/
private function registerError( ProcessingError $error ) {
$error->element = $this->options->getName();
$this->errors[] = $error;
ProcessingErrorHandler::addError( $error );
}
/**
* Validates and formats all the parameters (but aborts when a fatal error occurs).
*
* @since 0.4
* @deprecated since 1.0, use processParameters
*/
public function validateParameters() {
$this->doParamProcessing();
}
/**
* @since 1.0
*
* @return ProcessingResult
*/
public function processParameters() {
$this->doParamProcessing();
if ( !$this->hasFatalError() && $this->options->unknownIsInvalid() ) {
// Loop over the remaining raw parameters.
// These are unrecognized parameters, as they where not used by any parameter definition.
foreach ( $this->rawParameters as $paramName => $paramValue ) {
$this->registerNewError(
$paramName . ' is not a valid parameter', // TODO
$paramName
);
}
}
return $this->newProcessingResult();
}
/**
* @return ProcessingResult
*/
private function newProcessingResult() {
$parameters = [];
if ( !is_array( $this->params ) ) {
$this->params = [];
}
/**
* @var Param $parameter
*/
foreach ( $this->params as $parameter ) {
// TODO
$processedParam = new ProcessedParam(
$parameter->getName(),
$parameter->getValue(),
$parameter->wasSetToDefault()
);
// TODO: it is possible these values where set even when the value defaulted,
// so this logic is not correct and could be improved
if ( !$parameter->wasSetToDefault() ) {
$processedParam->setOriginalName( $parameter->getOriginalName() );
$processedParam->setOriginalValue( $parameter->getOriginalValue() );
}
$parameters[$processedParam->getName()] = $processedParam;
}
return new ProcessingResult(
$parameters,
$this->getErrors()
);
}
/**
* Does the actual parameter processing.
*
* @since 0.4
*/
private function doParamProcessing() {
$this->getParamsToProcess( [], $this->paramDefinitions );
while ( $this->paramsToHandle !== [] && !$this->hasFatalError() ) {
$this->processOneParam();
}
}
private function processOneParam() {
$paramName = array_shift( $this->paramsToHandle );
$definition = $this->paramDefinitions[$paramName];
$param = new Param( $definition );
$setUserValue = $this->attemptToSetUserValue( $param );
// If the parameter is required but not provided, register a fatal error and stop processing.
if ( !$setUserValue && $param->isRequired() ) {
$this->registerNewError(
"Required parameter '$paramName' is missing", // FIXME: i18n validator_error_required_missing
[ $paramName, 'missing' ],
ProcessingError::SEVERITY_FATAL
);
return;
}
$this->params[$param->getName()] = $param;
$initialSet = $this->paramDefinitions;
$param->process( $this->paramDefinitions, $this->params, $this->options );
foreach ( $param->getErrors() as $error ) {
$this->registerError( $error );
}
if ( $param->hasFatalError() ) {
return;
}
$this->getParamsToProcess( $initialSet, $this->paramDefinitions );
}
/**
* Gets an ordered list of parameters to process.
*
* @since 0.4
*
* @param array $initialParamSet
* @param array $resultingParamSet
*
* @throws \UnexpectedValueException
*/
private function getParamsToProcess( array $initialParamSet, array $resultingParamSet ) {
if ( $initialParamSet === [] ) {
$this->paramsToHandle = array_keys( $resultingParamSet );
}
else {
if ( !is_array( $this->paramsToHandle ) ) {
$this->paramsToHandle = [];
}
foreach ( $resultingParamSet as $paramName => $parameter ) {
if ( !array_key_exists( $paramName, $initialParamSet ) ) {
$this->paramsToHandle[] = $paramName;
}
}
}
$this->paramsToHandle = $this->getParameterNamesInEvaluationOrder( $this->paramDefinitions, $this->paramsToHandle );
}
/**
* @param IParamDefinition[] $paramDefinitions
* @param string[] $paramsToHandle
*
* @return array
*/
private function getParameterNamesInEvaluationOrder( array $paramDefinitions, array $paramsToHandle ) {
$dependencyList = [];
foreach ( $paramsToHandle as $paramName ) {
$dependencies = [];
if ( !array_key_exists( $paramName, $paramDefinitions ) ) {
throw new \UnexpectedValueException( 'Unexpected parameter name "' . $paramName . '"' );
}
if ( !is_object( $paramDefinitions[$paramName] ) || !( $paramDefinitions[$paramName] instanceof IParamDefinition ) ) {
throw new \UnexpectedValueException( 'Parameter "' . $paramName . '" is not a IParamDefinition' );
}
// Only include dependencies that are in the list of parameters to handle.
foreach ( $paramDefinitions[$paramName]->getDependencies() as $dependency ) {
if ( in_array( $dependency, $paramsToHandle ) ) {
$dependencies[] = $dependency;
}
}
$dependencyList[$paramName] = $dependencies;
}
$sorter = new TopologicalSort( $dependencyList, true );
return $sorter->doSort();
}
/**
* Tries to find a matching user provided value and, when found, assigns it
* to the parameter, and removes it from the raw values. Returns a boolean
* indicating if there was any user value set or not.
*
* @since 0.4
*
* @param Param $param
*
* @return boolean
*/
private function attemptToSetUserValue( Param $param ) {
if ( array_key_exists( $param->getName(), $this->rawParameters ) ) {
$param->setUserValue( $param->getName(), $this->rawParameters[$param->getName()], $this->options );
unset( $this->rawParameters[$param->getName()] );
return true;
}
else {
foreach ( $param->getDefinition()->getAliases() as $alias ) {
if ( array_key_exists( $alias, $this->rawParameters ) ) {
$param->setUserValue( $alias, $this->rawParameters[$alias], $this->options );
unset( $this->rawParameters[$alias] );
return true;
}
}
}
return false;
}
/**
* Returns the parameters.
*
* @since 0.4
* @deprecated since 1.0
*
* @return IParam[]
*/
public function getParameters() {
return $this->params;
}
/**
* Returns a single parameter.
*
* @since 0.4
* @deprecated since 1.0
*
* @param string $parameterName The name of the parameter to return
*
* @return IParam
*/
public function getParameter( $parameterName ) {
return $this->params[$parameterName];
}
/**
* Returns an associative array with the parameter names as key and their
* corresponding values as value.
*
* @since 0.4
*
* @return array
*/
public function getParameterValues() {
$parameters = [];
foreach ( $this->params as $parameter ) {
$parameters[$parameter->getName()] = $parameter->getValue();
}
return $parameters;
}
/**
* Returns the errors.
*
* @since 0.4
*
* @return ProcessingError[]
*/
public function getErrors() {
return $this->errors;
}
/**
* @since 0.4.6
*
* @return string[]
*/
public function getErrorMessages() {
$errors = [];
foreach ( $this->errors as $error ) {
$errors[] = $error->getMessage();
}
return $errors;
}
/**
* Returns if there where any errors during validation.
*
* @return boolean
*/
public function hasErrors() {
return !empty( $this->errors );
}
/**
* Returns false when there are no fatal errors or an ProcessingError when one is found.
*
* @return ProcessingError|boolean false
*/
public function hasFatalError() {
foreach ( $this->errors as $error ) {
if ( $error->isFatal() ) {
return $error;
}
}
return false;
}
}