Development

/branches/1.2/lib/validator/sfValidatorFromDescription.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/validator/sfValidatorFromDescription.class.php

Revision 9048, 9.6 kB (checked in by FabianLange, 7 years ago)

1.1: fixed @param phpdoc to fit specs in validator (refs #2991)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfValidatorFromDescription converts a string to a validator.
13  *
14  * @package    symfony
15  * @subpackage validator
16  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17  * @version    SVN: $Id$
18  */
19 class sfValidatorFromDescription extends sfValidatorDecorator
20 {
21   protected
22     $tokens = array(),
23     $string = '';
24
25   /**
26    * @see sfValidatorBase
27    */
28   public function __construct($string, $options = array(), $messages = array())
29   {
30     $this->string = $string;
31     $this->tokens = $this->tokenize($string);
32
33     parent::__construct($options, $messages);
34   }
35
36   /**
37    * Returns a PHP representation for the validator.
38    *
39    * This PHP representation can be evaled to return the object validator.
40    *
41    * This is mainly useful to cache the result of the validator string parsing.
42    *
43    * @return string The PHP representation for the validator
44    */
45   public function asPhp()
46   {
47     return $this->reduceTokens($this->tokens, 'asPhp');
48   }
49
50   /**
51    * @see sfValidatorDecorator
52    */
53   public function getValidator()
54   {
55     if (is_null($this->validator))
56     {
57       $this->validator = $this->reduceTokens($this->tokens, 'getValidator');
58     }
59
60     return $this->validator;
61   }
62
63   /**
64    * Tokenizes a validator string to a list of tokens in RPN.
65    *
66    * @param  string $string  A validator string
67    *
68    * @return array  An array of tokens
69    */
70   protected function tokenize($string)
71   {
72     $tokens = array();
73     $len = strlen($string);
74     $i = 0;
75     while ($i < $len)
76     {
77       if (preg_match('/^([a-z0-9_\-]+)\s*(<=|>=|<|>|==|!=)/i', substr($string, $i), $match))
78       {
79         // schema compare validator
80         $i += strlen($match[0]);
81
82         $leftField = $match[1];
83         $operator = $match[2];
84
85         // arguments (optional)
86         $arguments = $this->parseArguments($string, $i);
87
88         // rightField
89         if (!preg_match('/\s*([a-z0-9_\-]+)/', substr($string, $i), $match))
90         {
91           throw new DomainException('Parsing problem.');
92         }
93
94         $i += strlen($match[0]);
95         $rightField = $match[1];
96
97         $tokens[] = new sfValidatorFDToken('sfValidatorSchemaCompare', array($leftField, $operator, $rightField, $arguments[0], isset($arguments[1]) ? $arguments[1] : array()));
98       }
99       else if (preg_match('/^(and|or)/i', substr($string, $i), $match))
100       {
101         // all, any validador
102         $i += strlen($match[0]);
103
104         // arguments (optional)
105         $arguments = $this->parseArguments($string, $i);
106
107         $tokens[] = new sfValidatorFDTokenOperator(strtolower($match[1]), $arguments);
108       }
109       else if (preg_match('/^(?:([a-z0-9_\-]+)\:)?([a-z0-9_\-]+)/i', substr($string, $i), $match))
110       {
111         // single validator (optionally filtered)
112         $i += strlen($match[0]);
113
114         $class = 'sfValidator'.$match[2];
115         $arguments = $this->parseArguments($string, $i);
116         $token = new sfValidatorFDToken($class, array($arguments[0], isset($arguments[1]) ? $arguments[1] : array()));
117         if ($match[1])
118         {
119           $token = new sfValidatorFDTokenFilter($match[1], $token);
120         }
121
122         $tokens[] = $token;
123       }
124       else if ('(' == $string[$i])
125       {
126         $tokens[] = new sfValidatorFDTokenLeftBracket();
127         ++$i;
128       }
129       else if (')' == $string[$i])
130       {
131         $tokens[] = new sfValidatorFDTokenRightBracket();
132         ++$i;
133       }
134       else if (in_array($string[$i], array(' ', "\t", "\r", "\n")))
135       {
136         ++$i;
137       }
138       else
139       {
140         throw new DomainException(sprintf('Unable to parse string (%s).', $string));
141       }
142     }
143
144     return $this->convertInfixToRpn($tokens);
145   }
146
147   /**
148    * Parses validator arguments.
149    *
150    * @param  string  $string  The string to parse
151    * @param  integer $i       The indice to start the parsing
152    *
153    * @return array   An array of parameters
154    */
155   protected function parseArguments($string, &$i)
156   {
157     $len = strlen($string);
158
159     if ($i + 1 > $len || '(' != $string[$i])
160     {
161       return array(array(), array());
162     }
163
164     ++$i;
165
166     $args = '';
167     $opened = 0;
168     while ($i < $len)
169     {
170       if ('(' == $string[$i])
171       {
172         ++$opened;
173       }
174       else if (')' == $string[$i])
175       {
176         if (!$opened)
177         {
178           break;
179         }
180
181         --$opened;
182       }
183
184       $args .= $string[$i++];
185     }
186
187     ++$i;
188
189     return sfYamlInline::load('['.(!$args ? '{}' : $args).']');
190   }
191
192   /**
193    * Converts a token array from an infix notation to a RPN.
194    *
195    * @param  array $tokens  An array of tokens in infix notation
196    *
197    * @return array An array of token in RPN
198    */
199   protected function convertInfixToRpn($tokens)
200   {
201     $outputStack = array();
202     $operatorStack = array();
203     $precedences = array('and' => 2, 'or' => 1, '(' => 0);
204
205     // based on the shunting yard algorithm
206     foreach ($tokens as $token)
207     {
208       switch (get_class($token))
209       {
210         case 'sfValidatorFDToken':
211           $outputStack[] = $token;
212           break;
213         case 'sfValidatorFDTokenLeftBracket':
214           $operatorStack[] = $token;
215           break;
216         case 'sfValidatorFDTokenRightBracket':
217           while (!$operatorStack[count($operatorStack) - 1] instanceof sfValidatorFDTokenLeftBracket)
218           {
219             $outputStack[] = array_pop($operatorStack);
220           }
221           array_pop($operatorStack);
222           break;
223         case 'sfValidatorFDTokenOperator':
224           while (count($operatorStack) && $precedences[$token->__toString()] <= $precedences[$operatorStack[count($operatorStack) - 1]->__toString()])
225           {
226             $outputStack[] = array_pop($operatorStack);
227           }
228           $operatorStack[] = $token;
229           break;
230         default:
231           $outputStack[] = $token;
232       }
233     }
234
235     while (count($operatorStack))
236     {
237       $token = array_pop($operatorStack);
238       if ($token instanceof sfValidatorFDTokenLeftBracket || $token instanceof sfValidatorFDTokenRightBracket)
239       {
240         throw new DomainException(sprintf('Uneven parenthesis in string (%s).', $this->string));
241       }
242
243       $outputStack[] = $token;
244     }
245
246     return $outputStack;
247   }
248
249   /**
250    * Reduces tokens to a single token and convert it with the given method.
251    *
252    * @param  array  $tokens  An array of tokens
253    * @param  string $method  The method name to execute on each token
254    *
255    * @return mixed  A single validator representation
256    */
257   protected function reduceTokens($tokens, $method)
258   {
259     if (1 == count($tokens))
260     {
261       return $tokens[0]->$method();
262     }
263
264     // reduce to a single validator
265     while (count($tokens) > 1)
266     {
267       $i = 0;
268       while (isset($tokens[$i]) && !$tokens[$i] instanceof sfValidatorFDTokenOperator)
269       {
270         $i++;
271       }
272
273       $tokens[$i] = $tokens[$i]->$method($tokens[$i - 2], $tokens[$i - 1]);
274       unset($tokens[$i - 1], $tokens[$i - 2]);
275
276       $tokens = array_values($tokens);
277     }
278
279     return $tokens[0];
280   }
281 }
282
283 class sfValidatorFDToken
284 {
285   protected
286     $class,
287     $arguments;
288
289   public function __construct($class, $arguments = array())
290   {
291     $this->class = $class;
292     $this->arguments = $arguments;
293   }
294
295   public function asPhp()
296   {
297     return sprintf('new %s(%s)', $this->class, implode(', ', array_map(create_function('$a', 'return var_export($a, true);'), $this->arguments)));
298   }
299
300   public function getValidator()
301   {
302     $reflection = new ReflectionClass($this->class);
303
304     return $reflection->newInstanceArgs($this->arguments);
305   }
306 }
307
308 class sfValidatorFDTokenFilter
309 {
310   protected
311     $field,
312     $token;
313
314   public function __construct($field, sfValidatorFDToken $token)
315   {
316     $this->field = $field;
317     $this->token = $token;
318   }
319
320   public function asPhp()
321   {
322     return sprintf('new sfValidatorSchemaFilter(\'%s\', %s)', $this->field, $this->token->asPhp());
323   }
324
325   public function getValidator()
326   {
327     return new sfValidatorSchemaFilter($this->field, $this->token->getValidator());
328   }
329 }
330
331 class sfValidatorFDTokenOperator
332 {
333   protected
334     $class,
335     $operator,
336     $token;
337
338   public function __construct($operator, $arguments = array())
339   {
340     $this->operator = $operator;
341     $this->arguments = $arguments;
342     $this->class = 'or' == $operator ? 'sfValidatorOr' : 'sfValidatorAnd';
343   }
344
345   public function __toString()
346   {
347     return $this->operator;
348   }
349
350   public function asPhp($tokenLeft, $tokenRight)
351   {
352     return sprintf('new %s(array(%s, %s), %s)',
353       $this->class,
354       in_array(get_class($tokenLeft), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenLeft->asPhp() : $tokenLeft,
355       in_array(get_class($tokenRight), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenRight->asPhp() : $tokenRight,
356       implode(', ', array_map(create_function('$a', 'return var_export($a, true);'), $this->arguments))
357     );
358   }
359
360   public function getValidator($tokenLeft, $tokenRight)
361   {
362     $reflection = new ReflectionClass($this->class);
363
364     $validators = array(
365       in_array(get_class($tokenLeft), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenLeft->getValidator() : $tokenLeft,
366       in_array(get_class($tokenRight), array('sfValidatorFDToken', 'sfValidatorFDTokenFilter')) ? $tokenRight->getValidator() : $tokenRight,
367     );
368
369     return $reflection->newInstanceArgs(array_merge(array($validators), $this->arguments));
370   }
371 }
372
373 class sfValidatorFDTokenLeftBracket
374 {
375   public function __toString()
376   {
377     return '(';
378   }
379 }
380
381 class sfValidatorFDTokenRightBracket
382 {
383   public function __toString()
384   {
385     return ')';
386   }
387 }
388
Note: See TracBrowser for help on using the browser.