http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 18 LOC: 639 Statements: 319

Source file Statements Methods Total coverage
Parser.php 95.6% 100.0% 95.8%
   
1
<?php
2
/**
3
 * Xyster Framework
4
 *
5
 * This source file is subject to the new BSD license that is bundled
6
 * with this package in the file LICENSE.txt.
7
 * It is also available through the world-wide-web at this URL:
8
 * http://www.opensource.org/licenses/bsd-license.php
9
 *
10
 * @category  Xyster
11
 * @package   Xyster_Orm
12
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
13
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
14
 * @version   $Id: Parser.php 202 2008-01-20 16:20:09Z doublecompile $
15
 */
16
/**
17
 * @see Xyster_String
18
 */
19 1
require_once 'Xyster/String.php';
20
/**
21
 * A data entity: the basic data unit of the ORM package
22
 *
23
 * @category  XysterColumn
24
 * @package   Xyster_Orm
25
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
26
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
27
 */
28
class Xyster_Orm_Query_Parser
29
{
30
    /**
31
     * The mapper factory
32
     *
33
     * @var Xyster_Orm_Mapper_Factory_Interface
34
     */
35
    protected $_mapFactory;
36
37
    /**
38
     * A cache for runtime evaluations
39
     *
40
     * @var unknown_type
41
     */
42
    static protected $_runtime = array();
43
44
    /**
45
     * Creates a new query parser
46
     *
47
     * @param Xyster_Orm_Mapper_Factory_Interface $mapFactory
48
     */
49
    public function __construct( Xyster_Orm_Mapper_Factory_Interface $mapFactory )
50
    {
51 97
        $this->_mapFactory = $mapFactory;
52
    }
53
54
    /**
55
     * Asserts a field's presence in a class' members
56
     *
57
     * @param string $field
58
     * @param string $className
59
     * @throws Xyster_Orm_Query_Parser_Exception
60
     */
61
    public function assertValidFieldForClass( $field, $className )
62
    {
63 32
        require_once 'Xyster/Orm/Relation.php';
64 32
        require_once 'Xyster/Orm/Entity/Meta.php';
65
66 32
        require_once 'Xyster/Orm/Loader.php';
67 32
        Xyster_Orm_Loader::loadEntityClass($className);
68 32
        $map = $this->_mapFactory->get($className);
69 32
        $meta = $map->getEntityMeta();
70
71 32
        $field = ( $field instanceof Xyster_Data_Field ) ?
72 32
            trim($field->getName()) : trim($field);
73
74 32
        require_once 'Xyster/Data/Field/Aggregate.php';
75 32
        $matches = Xyster_Data_Field_Aggregate::match($field);
76 32
        if ( count($matches) ) {
77 1
            $field = trim($matches["field"]);
78 1
        }
79
80 32
        $calls = Xyster_String::smartSplit("->", $field);
81
        /*
82
            for composite references (i.e.  supervisor->name )
83
            - check each column exists in its container
84
        */
85 32
        if ( count($calls) > 1 ) {
86
87 7
            $container = $className;
88 7
            $currentMeta = $meta;
89 7
            foreach( $calls as $k=>$v ) {
90 7
                $this->assertValidFieldForClass($v, $container);
91 7
                if ( $meta->isRelation($v) ) {
92 7
                    $details = $meta->getRelation($v);
93 7
                    if ( !$details->isCollection() ) {
94 7
                        $container = $details->getTo();
95 7
                        $currentMeta = $this->_mapFactory->get($container)->getEntityMeta();
96 7
                    } else {
97 1
                        break;
98
                    }
99 7
                } else {
100 7
                    break;
101
                }
102 7
            }
103
104 7
        } else {
105
106
            /*
107
                for method calls
108
                - check method exists in class
109
                - check any method parameters that may themselves be members
110
            */
111 32
            if ( preg_match("/^(?P<name>[a-z0-9_]+)(?P<meth>\((?P<args>[\w\W]*)\))$/i", $field, $matches) ) {
112 16
                if ( $className != "" && !in_array($matches['name'], $meta->getMembers()) ) {
113 1
                    require_once 'Xyster/Orm/Query/Parser/Exception.php';
114 1
                    throw new Xyster_Orm_Query_Parser_Exception($matches['name'] . ' is not a member of the ' . $className . ' class' );
115 0
                }
116 16
                $args = array();
117 16
                if ( strlen(trim($matches['args'])) ) {
118 9
                    foreach( Xyster_String::smartSplit(",", $matches['args']) as $v ) {
119 9
                        $v = trim($v);
120 9
                        $args[] = ( $this->isValidField($v) ) ?
121 1
                            $this->assertValidFieldForClass($v, $className) : $v;
122 9
                    }
123 9
                }
124
            /*
125
                for properties and relationships
126
                - check column exists in class
127
            */
128 32
            } else if ( $className != "" && !in_array($field, $meta->getMembers()) ) {
129 1
                require_once 'Xyster/Orm/Query/Parser/Exception.php';
130 1
                throw new Xyster_Orm_Query_Parser_Exception($field . ' is not a member of the ' . $className . ' class');
131 0
            }
132
        }
133
    }
134
135
    /**
136
     * Checks if a literal is a method call
137
     *
138
     * @param string $field
139
     * @return bool
140
     */
141
    public function isMethodCall( $field )
142
    {
143 15
        return (bool) preg_match("/^[a-z_][a-z0-9_]*\([\w\W]*\)$/i", $field);
144
    }
145
146
    /**
147
     * Verifies if a {@link Xyster_Data_Criterion} or {@link Xyster_Data_Field} is runtime
148
     *
149
     * @param object $object
150
     * @param string $class
151
     * @return bool
152
     */
153
    public function isRuntime( $object, $class )
154
    {
155 33
        if ( $object instanceof Xyster_Data_Criterion ) {
156
157 20
            foreach( Xyster_Data_Criterion::getFields($object) as $v ) {
158 20
                if ($this->isRuntime($v, $class)) {
159 12
                    return true;
160 0
                }
161 16
            }
162 16
            return false;
163
164 33
        } else if ( $object instanceof Xyster_Data_Field ) {
165
166 32
            $name = $object->getName();
167 32
            if ( !isset(self::$_runtime[$class][$name])) {
168 14
                self::$_runtime[$class][$name] = $this->_isRuntime($name,$class);
169 14
            }
170 32
            return self::$_runtime[$class][$name];
171
172 13
        } else if ( $object instanceof Xyster_Data_Sort ) {
173
174 12
            return $this->isRuntime($object->getField(), $class);
175
176 0
        }
177
178 1
        require_once 'Xyster/Orm/Query/Parser/Exception.php';
179 1
        throw new Xyster_Orm_Query_Parser_Exception('Unexpected type: ' . gettype($object));
180
    }
181
182
    /**
183
     * Checks a reference for syntactical correctness
184
     *
185
     * @param string $field
186
     * @return boolean
187
     */
188
    public function isValidField( $field )
189
    {
190 16
        $field = trim($field);
191
192 16
        $ok = true;
193 16
        if ( !preg_match("/^[a-z][a-z0-9_]*(->[a-z0-9_]+(\([\s]*\))?)*$/i", $field) ) {
194 16
            $mcs = Xyster_String::smartSplit("->", $field);
195 16
            foreach( $mcs as $mc ) {
196 16
                $matches = array();
197 16
                $match = preg_match( "/^[a-z][a-z0-9_]*(\((?P<params>[\w\W]*)\))?$/i", $mc, $matches );
198 16
                if ( ( $match && array_key_exists("params", $matches) && strlen(trim($matches['params']))
199 16
                && !$this->_checkMethodParameters($matches['params']) ) || !$match ) {
200 16
                    $ok = false;
201 16
                    break;
202 0
                }
203 1
            }
204 16
        }
205 16
        return $ok;
206
    }
207
208
    /**
209
     * Parse a statement into a Xyster_Data_Criterion
210
     *
211
     * @param string $statement
212
     * @return Xyster_Data_Criterion
213
     */
214
    public function parseCriterion( $statement )
215
    {
216 54
        require_once 'Xyster/Data/Junction.php';
217
218 54
        $crit = null;
219 54
        $statement = trim($statement);
220
221 54
        $groups = Xyster_String::matchGroups($statement);
222 54
        if ( count($groups) == 1 && strlen($groups[0]) == strlen($statement) ) {
223 35
            $statement = trim(substr($statement,1,-1));
224 35
        }
225
226 54
        $crits = Xyster_String::smartSplit(" AND ", $statement, true);
227
        // in case it split the and of a "BETWEEN x AND y"
228 54
        if ( count($crits) == 2 && $this->_checkLiteral($crits[1]) ) {
229 1
            $crits = array($statement);
230 1
        }
231
232 54
        if ( count($crits) < 2 ) {
233
234 54
            $subcrits = Xyster_String::smartSplit(" OR ", $statement, true);
235 54
            if ( count($subcrits) < 2 ) {
236 54
                $groups = Xyster_String::matchGroups(trim($subcrits[0]));
237 54
                $crit = ( count($groups) > 0 && strlen($groups[0]) ) ?
238 2
                    $this->parseCriterion($subcrits[0]) :
239 54
                    $this->parseExpression($subcrits[0]);
240 54
            } else {
241 2
                $criteria = array_map(array($this, 'parseCriterion'), $subcrits);
242 2
                $crit = Xyster_Data_Criterion::fromArray('OR', $criteria);
243
            }
244
245 54
        } else {
246
247 5
            $crit = Xyster_Data_Junction::all( $this->parseCriterion($crits[0]),
248 5
                $this->parseCriterion($crits[1]) );
249
250 5
            if ( count($crits) > 2 ) {
251 1
                for ( $i=2; $i<count($crits); $i++ ) {
252 1
                    $crit->add( $this->parseCriterion( $crits[$i] ) );
253 1
                }
254 1
            }
255
        }
256
257 54
        return $crit;
258
    }
259
260
    /**
261
     * Parse string statement as Expression
262
     *
263
     * @param string $statement
264
     * @return Xyster_Data_Expression
265
     * @throws Xyster_Orm_Query_Parser_Exception if the expression syntax is incorrect
266
     */
267
    public function parseExpression( $statement )
268
    {
269 59
        require_once 'Xyster/Data/Expression.php';
270
271
        // remove whitespace characters we don't like
272 59
        $statement = Xyster_String::smartSplit(" ", preg_replace("/[\t\n\r]+/", " ", trim($statement)));
273
274 59
        $exp = array();
275 59
        foreach( $statement as $v ) {
276 59
            if( $v != "" ) {
277 59
                $exp[] = $v;
278 59
            }
279 59
        }
280
281 59
        $leftlit = $this->parseField($exp[0]);
282
283 59
        array_shift($exp);
284 59
        $upper0 = strtoupper($exp[0]);
285 59
        $upper1 = strtoupper($exp[1]);
286
287 59
        if ( $upper0 == "NOT" || ( $upper0 == "IS" && $upper1 == "NOT" ) ) {
288 2
            $operator = $upper0 . " " . $upper1;
289 2
            array_shift($exp);
290 2
        } else {
291 58
            $operator = $upper0;
292
        }
293
294 59
        if ( !Xyster_Data_Expression::isOperator($operator) ) {
295 1
            require_once 'Xyster/Orm/Query/Parser/Exception.php';
296 1
            throw new Xyster_Orm_Query_Parser_Exception('Invalid expression operator: ' . $operator);
297 0
        }
298
299 58
        array_shift($exp);
300
301 58
        if ( ( $operator == "BETWEEN" || $operator == "NOT BETWEEN" ) && count($exp) != 3 ) {
302 1
            require_once 'Xyster/Orm/Query/Parser/Exception.php';
303 1
            throw new Xyster_Orm_Query_Parser_Exception('Invalid literal: ' . implode(" ",$exp));
304 0
        }
305
306 57
        if ( $operator == "IN" || $operator == "NOT IN" ) {
307 2
            $rightlit = trim(implode(" ",$exp));
308 2
            $matches = array();
309 2
            if ( !preg_match('/^\([\s]*(?P<choices>.*)[\s]*\)$/',$rightlit,$matches) ) {
310 1
                require_once 'Xyster/Orm/Query/Parser/Exception.php';
311 1
                throw new Xyster_Orm_Query_Parser_Exception('Invalid literal: ' . $rightlit);
312 0
            }
313
            else {
314 1
                $inChoices = Xyster_String::smartSplit(',',$matches['choices']);
315 1
                foreach( $inChoices as $k=>$choice ) {
316 1
                    $choice = trim($choice);
317 1
                    $this->_assertLiteral($choice);
318 1
                    if ( preg_match('/^"[^"]*"$/i',$choice) )
319 1
                        $inChoices[$k] = substr($choice,1,-1);
320 1
                }
321 1
                $rightlit = $inChoices;
322
            }
323 1
        } else {
324 55
            $rightlit = ( $operator == "BETWEEN" || $operator == "NOT BETWEEN" ) ?
325 55
                array( $exp[0], $exp[2] ) : $exp[0];
326 55
            $this->_assertLiteral($rightlit);
327
        }
328
329 55
        if ( !is_array($rightlit) && preg_match('/^"[^"]*"$/i',$rightlit) ) {
330 7
            $rightlit = substr($rightlit,1,-1);
331 7
        }
332
333 55
        $args = array( $leftlit );
334 55
        if ( in_array($operator, array("=","<>",">",">=","<","<=","LIKE","NOT LIKE",'IN','NOT IN')) ) {
335 55
            $args[] = $rightlit;
336 55
        } else if ( $operator == "BETWEEN" || $operator == "NOT BETWEEN" ) {
337 1
            $args[] = $rightlit[0];
338 1
            $args[] = $rightlit[1];
339 1
        }
340
341 55
        return call_user_func_array(array("Xyster_Data_Expression",
342 55
            Xyster_Data_Expression::getMethodName($operator)), $args);
343
    }
344
345
    /**
346
     * Parse a string as Field
347
     *
348
     * @param string $name
349
     * @return Xyster_Data_Field
350
     */
351
    public function parseField( $name )
352
    {
353 62
        $name = trim($name);
354
355 62
        require_once 'Xyster/Data/Field/Aggregate.php';
356 62
        $match = Xyster_Data_Field_Aggregate::match($name);
357
358 62
        if ( $match ) {
359 3
            require_once 'Xyster/Enum.php';
360 3
            $function = Xyster_Enum::valueOf('Xyster_Data_Aggregate', strtoupper($match['function']));
361 3
            $field = Xyster_Data_Field::aggregate($function, trim($match['field']));
362 3
        } else {
363 61
            $field = Xyster_Data_Field::named($name);
364
        }
365
366 62
        return $field;
367
    }
368
369
    /**
370
     * Parse a string as Field with alias
371
     *
372
     * @param string $name
373
     * @return Xyster_Data_Field
374
     */
375
    public function parseFieldAlias( $statement )
376
    {
377 3
        $statement = trim($statement);
378 3
        $matches = array();
379 3
        $alias = $statement;
380 3
        $pattern = '/[\s]+(AS[\s]+(?P<aliasA>[a-z0-9_]+)|"(?P<aliasQ>[a-z0-9_]+)")/i';
381
382 3
        if (preg_match($pattern, $statement, $matches)) {
383 2
            if (!empty($matches['aliasA']) || !empty($matches['aliasQ'])) {
384 2
                $alias = (!empty($matches['aliasA'])) ?
385 2
                    $matches['aliasA'] : $matches['aliasQ'];
386 2
            }
387 2
            $statement = str_replace($matches[0], "", $statement);
388 2
        }
389
390 3
        $field = $this->parseField($statement);
391 3
        $field->setAlias($alias);
392 3
        return $field;
393
    }
394
395
    /**
396
     * Parses string statement as Xyster_Orm_Query
397
     *
398
     * @param Xyster_Orm_Query $query
399
     * @param string $statement
400
     */
401
    public function parseQuery( Xyster_Orm_Query $query, $statement )
402
    {
403 2
        $expecting = array('where','order');
404
405 2
        $parts = $this->_baseParseQuery($query, $statement, $expecting);
406 2
        if ( !empty($parts['where']) ) {
407 2
            $query->where($this->parseCriterion($parts['where']));
408 2
        }
409 2
        if ( !empty($parts['order']) ) {
410 1
            $this->_parseClause($query, 'order', $parts['order']);
411 1
        }
412
    }
413
414
    /**
415
     * Parses string statement as Xyster_Orm_Query_Report
416
     *
417
     * @param Xyster_Orm_Query_Report $query
418
     * @param string $statement
419
     */
420
    public function parseReportQuery( Xyster_Orm_Query_Report $query, $statement )
421
    {
422 3
        $expecting = array('select','where','group','having','order');
423
424 3
        $parts = $this->_baseParseQuery($query,$statement,$expecting);
425 3
        if ( empty($parts['select']) ) {
426 1
            require_once 'Xyster/Orm/Query/Parser/Exception.php';
427 1
            throw new Xyster_Orm_Query_Parser_Exception('Invalid statement: ' . $statement);
428 0
        }
429
430 2
        $matches = array();
431 2
        if (preg_match('/^distinct[[:space:]]+/i', $parts['select'], $matches)) {
432 1
            $query->distinct(true);
433 1
            $parts['select'] = str_replace($matches[0], '', $parts['select']);
434 1
        }
435
436 2
        $this->_parseClause($query, 'select', $parts['select']);
437 2
        if ( !empty($parts['where']) ) {
438 2
            $query->where($this->parseCriterion($parts['where']));
439 2
        }
440 2
        if ( !empty($parts['order']) ) {
441 1
            $this->_parseClause($query, 'order', $parts['order']);
442 1
        }
443 2
        if ( !empty($parts['group']) ) {
444 1
            $this->_parseClause($query, 'group', $parts['group']);
445 1
        }
446 2
        if ( !empty($parts['having']) ) {
447 1
            $query->having($this->parseCriterion($parts['having']));
448 1
        }
449
    }
450
451
    /**
452
     * Parse string statement as a {@link Xyster_Data_Sort}
453
     *
454
     * @param string $statement
455
     * @return Xyster_Data_Sort
456
     * @throws Xyster_Orm_Query_Parser_Exception  if the statement syntax is invalid
457
     */
458
    public function parseSort( $statement )
459
    {
460 3
        $statement = trim($statement);
461 3
        $matches = array();
462 3
        $dir = 'ASC';
463
464 3
        if ( preg_match('/\s+(?P<dir>ASC|DESC)$/i', $statement, $matches) ) {
465 3
            $dir = $matches["dir"];
466 3
            $statement = trim(str_replace($matches[0], "", $statement));
467 3
        }
468
469 3
        $field = $this->parseField($statement);
470 3
        return (!strcasecmp($dir, 'DESC')) ? $field->desc() : $field->asc();
471
    }
472
473
    /**
474
     * Asserts a literal for syntactical correctness
475
     *
476
     * @param string $lit
477
     * @throws Xyster_Orm_Query_Parser_Exception if the syntax is incorrect
478
     */
479
    protected function _assertLiteral( $lit )
480
    {
481 56
        if ( is_array($lit) ) {
482 1
            foreach( $lit as $v ) {
483 1
                $this->_assertLiteral($v);
484 1
            }
485 56
        } else if ( !$this->_checkLiteral($lit) ) {
486 1
            require_once 'Xyster/Orm/Query/Parser/Exception.php';
487 1
            throw new Xyster_Orm_Query_Parser_Exception('Invalid literal: ' . $lit);
488 0
        }
489
    }
490
491
    /**
492
     * Removes the limit and offset clause from a statement
493
     *
494
     * @param Xyster_Orm_Query $query  The query into which the parts are set
495
     * @param string $statement  The statement to parse
496
     * @param array $expecting
497
     * @return array  The statement split by parts
498
     */
499
    protected function _baseParseQuery( Xyster_Orm_Query $query, $statement, array $expecting )
500
    {
501 4
        $matches = array();
502 4
        if ( preg_match('/[\s]*LIMIT (?P<limit>[\d]+)( OFFSET (?P<offset>[\d]+))?$/i',$statement,$matches) ) {
503 2
            $statement = str_replace($matches[0], '', $statement);
504 2
        }
505 4
        $limit = ( isset($matches['limit']) ) ? $matches['limit'] : 0;
506 4
        $offset = ( isset($matches['offset']) ) ? $matches['offset'] : 0;
507 4
        if ( $limit ) {
508 2
            $query->limit($limit,$offset);
509 2
        }
510
511 4
        $parts = array();
512 4
        $part = '';
513 4
        $split = Xyster_String::smartSplit(' ', trim($statement));
514
515 4
        foreach( $split as $v ) {
516 4
            if ( in_array($part, array('order','group')) && !strcasecmp($v, 'by') ) {
517 2
                continue;
518 0
            }
519 4
            foreach( $expecting as $epart ) {
520 4
                if ( !strcasecmp($v, $epart) ) {
521 3
                    $part = strtolower($v);
522 3
                }
523 4
            }
524 4
            if ( !in_array(strtolower($v), $expecting) ) {
525 4
                if ( !isset($parts[$part]) ) {
526 4
                    $parts[$part] = "";
527 4
                }
528 4
                $parts[$part] .= $v . " ";
529 4
            }
530 4
        }
531
532 4
        return $parts;
533
    }
534
535
    /**
536
     * Checks a literal for syntactical correctness
537
     *
538
     * @param string $lit
539
     * @return boolean
540
     */
541
    protected function _checkLiteral( $lit )
542
    {
543
        return (
544
            // either a string or a number or the word "null"
545 57
            preg_match("/^(\"[^\"]*\"|[\d]+(.[\d]+)?|null)$/i", trim($lit))
546
            //  a string with escapes in it
547 7
            || preg_match('/^"[^"\\\\]*(\\\\.[^"\\\\]*)*"$/', trim($lit))
548
            // check to see if it's a field
549 7
            || $this->isValidField($lit)
550 57
        );
551
    }
552
553
    /**
554
     * Checks method parameters for syntactical correctness
555
     *
556
     * @param array $params
557
     * @return boolean
558
     */
559
    protected function _checkMethodParameters( $params )
560
    {
561 1
        $ps = Xyster_String::smartSplit(",", trim($params));
562 1
        $ok = true;
563 1
        foreach( $ps as $p ) {
564 1
            if ( !$this->_checkLiteral($p) ) {
565 1
                $ok = false;
566 1
                break;
567 0
            }
568 1
        }
569 1
        return $ok;
570
    }
571
572
    /**
573
     * Checks to see if a column can only be evaluated at runtime
574
     *
575
     * @param string $field
576
     * @param string $className
577
     * @return boolean
578
     */
579
    protected function _isRuntime( $field, $className )
580
    {
581 14
        require_once 'Xyster/Orm/Loader.php';
582 14
        Xyster_Orm_Loader::loadEntityClass($className);
583 14
        $meta = $this->_mapFactory->get($className)->getEntityMeta();
584
585 14
        $calls = Xyster_String::smartSplit('->',trim($field));
586
587 14
        if ( count($calls) == 1 ) {
588
589
            // the call isn't composite - could be a member or a relation
590 14
            return ( $this->isMethodCall($calls[0]) ) ? true :
591 8
                ( !in_array($field, $meta->getFieldNames())
592 14
                    && !$meta->isRelation($field) );
593
594 0
        } else {
595
596
            // the call is composite - loop through to see if we can figure
597
            // out the type bindings
598 3
            $container = $className;
599 3
            $currentMeta = $meta;
600 3
            foreach( $calls as $call ) {
601 3
                if ( $this->isMethodCall($call) ) {
602 1
                    return true;
603 0
                } else {
604 3
                    $isRel = $currentMeta->isRelation($call);
605 3
                    if ( !in_array($call, array_keys($currentMeta->getFields()))
606 3
                        && !$isRel ) {
607 1
                        return true;
608 3
                    } else if ( $isRel ) {
609 3
                        $container = $currentMeta->getRelation($call)->getTo();
610 3
                        $currentMeta = $this->_mapFactory->get($container)->getEntityMeta();
611 3
                    }
612
                }
613 3
            }
614 3
            return false;
615
616
        }
617
    }
618
619
    /**
620
     * Parses a string statement clause into its corresponding parts
621
     *
622
     * @param Xyster_Orm_Query $query The query into which the parts will be set
623
     * @param string $type  The type of clause (either select, group, or order)
624
     * @param string $statement  The actual clause to parse
625
     */
626
    protected function _parseClause( Xyster_Orm_Query $query, $type, $statement )
627
    {
628
        $call = array(
629 3
                'select'=>array($this, 'parseFieldAlias'),
630 3
                'order'=>array($this, 'parseSort'),
631 3
                'group'=>array('Xyster_Data_Field', 'group')
632 3
            );
633 3
        $method = array('select'=>'field', 'order'=>'order', 'group'=>'group');
634
635 3
        foreach( Xyster_String::smartSplit(",", $statement) as $item ) {
636 3
            $query->{$method[$type]}( call_user_func($call[$type], $item) );
637 3
        }
638
    }
639
}


Report generated at 2008-01-20T12:13:38-05:00