* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * sfValidatorFromDescription converts a string to a validator. * * @package symfony * @subpackage validator * @author Fabien Potencier * @version SVN: $Id: sfValidatorFromDescription.class.php 28700 2010-03-23 11:57:16Z fabien $ */ class sfValidatorFromDescription extends sfValidatorDecorator { protected $tokens = array(), $string = ''; /** * @see sfValidatorBase */ public function __construct($string, $options = array(), $messages = array()) { $this->string = $string; $this->tokens = $this->tokenize($string); parent::__construct($options, $messages); } /** * Returns a PHP representation for the validator. * * This PHP representation can be evaled to return the object validator. * * This is mainly useful to cache the result of the validator string parsing. * * @return string The PHP representation for the validator */ public function asPhp() { return $this->reduceTokens($this->tokens, 'asPhp'); } /** * @see sfValidatorDecorator */ public function getValidator() { if (null === $this->validator) { $this->validator = $this->reduceTokens($this->tokens, 'getValidator'); } return $this->validator; } /** * Tokenizes a validator string to a list of tokens in RPN. * * @param string $string A validator string * * @return array An array of tokens */ protected function tokenize($string) { $tokens = array(); $len = strlen($string); $i = 0; while ($i < $len) { if (preg_match('/^([a-z0-9_\-]+)\s*(<=|>=|<|>|==|!=)/i', substr($string, $i), $match)) { // schema compare validator $i += strlen($match[0]); $leftField = $match[1]; $operator = $match[2]; // arguments (optional) $arguments = $this->parseArguments($string, $i); // rightField if (!preg_match('/\s*([a-z0-9_\-]+)/', substr($string, $i), $match)) { throw new DomainException('Parsing problem.'); } $i += strlen($match[0]); $rightField = $match[1]; $tokens[] = new sfValidatorFDToken('sfValidatorSchemaCompare', array($leftField, $operator, $rightField, $arguments[0], isset($arguments[1]) ? $arguments[1] : array())); } else if (preg_match('/^(and|or)/i', substr($string, $i), $match)) { // all, any validador $i += strlen($match[0]); // arguments (optional) $arguments = $this->parseArguments($string, $i); $tokens[] = new sfValidatorFDTokenOperator(strtolower($match[1]), $arguments); } else if (preg_match('/^(?:([a-z0-9_\-]+)\:)?([a-z0-9_\-]+)/i', substr($string, $i), $match)) { // single validator (optionally filtered) $i += strlen($match[0]); $class = 'sfValidator'.$match[2]; $arguments = $this->parseArguments($string, $i); $token = new sfValidatorFDToken($class, array($arguments[0], isset($arguments[1]) ? $arguments[1] : array())); if ($match[1]) { $token = new sfValidatorFDTokenFilter($match[1], $token); } $tokens[] = $token; } else if ('(' == $string[$i]) { $tokens[] = new sfValidatorFDTokenLeftBracket(); ++$i; } else if (')' == $string[$i]) { $tokens[] = new sfValidatorFDTokenRightBracket(); ++$i; } else if (in_array($string[$i], array(' ', "\t", "\r", "\n"))) { ++$i; } else { throw new DomainException(sprintf('Unable to parse string (%s).', $string)); } } return $this->convertInfixToRpn($tokens); } /** * Parses validator arguments. * * @param string $string The string to parse * @param integer $i The indice to start the parsing * * @return array An array of parameters */ protected function parseArguments($string, &$i) { $len = strlen($string); if ($i + 1 > $len || '(' != $string[$i]) { return array(array(), array()); } ++$i; $args = ''; $opened = 0; while ($i < $len) { if ('(' == $string[$i]) { ++$opened; } else if (')' == $string[$i]) { if (!$opened) { break; } --$opened; } $args .= $string[$i++]; } ++$i; return sfYamlInline::load('['.(!$args ? '{}' : $args).']'); } /** * Converts a token array from an infix notation to a RPN. * * @param array $tokens An array of tokens in infix notation * * @return array An array of token in RPN */ protected function convertInfixToRpn($tokens) { $outputStack = array(); $operatorStack = array(); $precedences = array('and' => 2, 'or' => 1, '(' => 0); // based on the shunting yard algorithm foreach ($tokens as $token) { switch (get_class($token)) { case 'sfValidatorFDToken': $outputStack[] = $token; break; case 'sfValidatorFDTokenLeftBracket': $operatorStack[] = $token; break; case 'sfValidatorFDTokenRightBracket': while (!$operatorStack[count($operatorStack) - 1] instanceof sfValidatorFDTokenLeftBracket) { $outputStack[] = array_pop($operatorStack); } array_pop($operatorStack); break; case 'sfValidatorFDTokenOperator': while (count($operatorStack) && $precedences[$token->__toString()] <= $precedences[$operatorStack[count($operatorStack) - 1]->__toString()]) { $outputStack[] = array_pop($operatorStack); } $operatorStack[] = $token; break; default: $outputStack[] = $token; } } while (count($operatorStack)) { $token = array_pop($operatorStack); if ($token instanceof sfValidatorFDTokenLeftBracket || $token instanceof sfValidatorFDTokenRightBracket) { throw new DomainException(sprintf('Uneven parenthesis in string (%s).', $this->string)); } $outputStack[] = $token; } return $outputStack; } /** * Reduces tokens to a single token and convert it with the given method. * * @param array $tokens An array of tokens * @param string $method The method name to execute on each token * * @return mixed A single validator representation */ protected function reduceTokens($tokens, $method) { if (1 == count($tokens)) { return $tokens[0]->$method(); } // reduce to a single validator while (count($tokens) > 1) { $i = 0; while (isset($tokens[$i]) && !$tokens[$i] instanceof sfValidatorFDTokenOperator) { $i++; } $tokens[$i] = $tokens[$i]->$method($tokens[$i - 2], $tokens[$i - 1]); unset($tokens[$i - 1], $tokens[$i - 2]); $tokens = array_values($tokens); } return $tokens[0]; } } class sfValidatorFDToken { protected $class, $arguments; public function __construct($class, $arguments = array()) { $this->class = $class; $this->arguments = $arguments; } public function asPhp() { return sprintf('new %s(%s)', $this->class, implode(', ', array_map(create_function('$a', 'return var_export($a, true);'), $this->arguments))); } public function getValidator() { $reflection = new ReflectionClass($this->class); return $reflection->newInstanceArgs($this->arguments); } } class sfValidatorFDTokenFilter { protected $field, $token; public function __construct($field, sfValidatorFDToken $token) { $this->field = $field; $this->token = $token; } public function asPhp() { return sprintf('new sfValidatorSchemaFilter(\'%s\', %s)', $this->field, $this->token->asPhp()); } public function getValidator() { return new sfValidatorSchemaFilter($this->field, $this->token->getValidator()); } } class sfValidatorFDTokenOperator { protected $class, $operator, $token; public function __construct($operator, $arguments = array()) { $this->operator = $operator; $this->arguments = $arguments; $this->class = 'or' == $operator ? 'sfValidatorOr' : 'sfValidatorAnd'; } public function __toString() { return $this->operator; } public function asPhp($tokenLeft, $tokenRight) { return sprintf('new %s(array(%s, %s), %s)', $this->class, is_object($tokenLeft) && in_array(get_class($tokenLeft), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenLeft->asPhp() : $tokenLeft, is_object($tokenRight) && in_array(get_class($tokenRight), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenRight->asPhp() : $tokenRight, implode(', ', array_map(create_function('$a', 'return var_export($a, true);'), $this->arguments)) ); } public function getValidator($tokenLeft, $tokenRight) { $reflection = new ReflectionClass($this->class); $validators = array( in_array(get_class($tokenLeft), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenLeft->getValidator() : $tokenLeft, in_array(get_class($tokenRight), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenRight->getValidator() : $tokenRight, ); return $reflection->newInstanceArgs(array_merge(array($validators), $this->arguments)); } } class sfValidatorFDTokenLeftBracket { public function __toString() { return '('; } } class sfValidatorFDTokenRightBracket { public function __toString() { return ')'; } }