Coverage for Doctrine_Query_Tokenizer

Back to coverage report

1 <?php
2 /*
3  *  $Id$
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information, see
19  * <http://www.phpdoctrine.com>.
20  */
21
22 /**
23  * Doctrine_Query_Tokenizer
24  *
25  * @package     Doctrine
26  * @subpackage  Query
27  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
28  * @link        www.phpdoctrine.com
29  * @since       1.0
30  * @version     $Revision$
31  * @todo Give the tokenizer state, make it better work together with Doctrine_Query and maybe
32  *       take out commonly used string manipulation methods
33  *       into a stateless StringUtil? class. This tokenizer should be concerned with tokenizing
34  *       DQL strings.
35  */
36 class Doctrine_Query_Tokenizer 
37 {
38     
39     /**
40      * tokenizeQuery
41      * splits the given dql query into an array where keys
42      * represent different query part names and values are
43      * arrays splitted using sqlExplode method
44      *
45      * example:
46      *
47      * parameter:
48      *      $query = "SELECT u.* FROM User u WHERE u.name LIKE ?"
49      * returns:
50      *      array('select' => array('u.*'),
51      *            'from'   => array('User', 'u'),
52      *            'where'  => array('u.name', 'LIKE', '?'))
53      *
54      * @param string $query                 DQL query
55      * @throws Doctrine_Query_Exception     if some generic parsing error occurs
56      * @return array                        an array containing the query string parts
57      */
58     public function tokenizeQuery($query)
59     {
60         $parts = array();
61         $tokens = $this->sqlExplode($query, ' ');
62
63         foreach ($tokens as $index => $token) {
64             $token = trim($token);
65             switch (strtolower($token)) {
66                 case 'delete':
67                 case 'update':
68                 case 'select':
69                 case 'set':
70                 case 'from':
71                 case 'where':
72                 case 'limit':
73                 case 'offset':
74                 case 'having':
75                     $p = $token;
76                     //$parts[$token] = array();
77                     $parts[$token] = '';
78                 break;
79                 case 'order':
80                 case 'group':
81                     $i = ($index + 1);
82                     if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
83                         $p = $token;
84                         $parts[$token] = '';
85                         //$parts[$token] = array();
86                     } else {
87                         $parts[$p] .= "$token ";
88                         //$parts[$p][] = $token;
89                     }
90                 break;
91                 case 'by':
92                     continue;
93                 default:
94                     if ( ! isset($p)) {
95                         throw new Doctrine_Query_Tokenizer_Exception(
96                                 "Couldn't tokenize query. Encountered invalid token: '$token'.");
97                     }
98
99                     $parts[$p] .= "$token ";
100                     //$parts[$p][] = $token;
101             }
102         }
103         return $parts;
104     }
105     
106     /**
107      * trims brackets
108      *
109      * @param string $str
110      * @param string $e1        the first bracket, usually '('
111      * @param string $e2        the second bracket, usually ')'
112      */
113     public function bracketTrim($str, $e1 = '(', $e2 = ')')
114     {
115         if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) {
116             return substr($str, 1, -1);
117         } else {
118             return $str;
119         }
120     }
121
122     /**
123      * bracketExplode
124      *
125      * example:
126      *
127      * parameters:
128      *      $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com'
129      *      $d = ' AND '
130      *      $e1 = '('
131      *      $e2 = ')'
132      *
133      * would return an array:
134      *      array("(age < 20 AND age > 18)",
135      *            "email LIKE 'John@example.com'")
136      *
137      * @param string $str
138      * @param string $d         the delimeter which explodes the string
139      * @param string $e1        the first bracket, usually '('
140      * @param string $e2        the second bracket, usually ')'
141      *
142      */
143     public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
144     {
145         if (is_array($d)) {
146             $a = preg_split('#('.implode('|', $d).')#', $str);
147             $d = stripslashes($d[0]);
148         } else {
149             $a = explode($d, $str);
150         }
151
152         $i = 0;
153         $term = array();
154         foreach($a as $key=>$val) {
155             if (empty($term[$i])) {
156                 $term[$i] = trim($val);
157                 $s1 = substr_count($term[$i], $e1);
158                 $s2 = substr_count($term[$i], $e2);
159                 
160                 if ($s1 == $s2) {
161                     $i++;
162                 }
163             } else {
164                 $term[$i] .= $d . trim($val);
165                 $c1 = substr_count($term[$i], $e1);
166                 $c2 = substr_count($term[$i], $e2);
167                 
168                 if ($c1 == $c2) { 
169                     $i++;
170                 }
171             }
172         }
173         return $term;
174     }
175
176     /**
177      * quoteExplode
178      *
179      * example:
180      *
181      * parameters:
182      *      $str = email LIKE 'John@example.com'
183      *      $d = ' LIKE '
184      *
185      * would return an array:
186      *      array("email", "LIKE", "'John@example.com'")
187      *
188      * @param string $str
189      * @param string $d         the delimeter which explodes the string
190      */
191     public function quoteExplode($str, $d = ' ')
192     {
193         if (is_array($d)) {
194             $a = preg_split('/('.implode('|', $d).')/', $str);
195             $d = stripslashes($d[0]);
196         } else {
197             $a = explode($d, $str);
198         }
199
200         $i = 0;
201         $term = array();
202         foreach ($a as $key => $val) {
203             if (empty($term[$i])) {
204                 $term[$i] = trim($val);
205
206                 if ( ! (substr_count($term[$i], "'") & 1)) {
207                     $i++;
208                 }
209             } else {
210                 $term[$i] .= $d . trim($val);
211
212                 if ( ! (substr_count($term[$i], "'") & 1)) {
213                     $i++;
214                 }
215             }
216         }
217         return $term;
218     }
219
220     /**
221      * sqlExplode
222      *
223      * explodes a string into array using custom brackets and
224      * quote delimeters
225      *
226      *
227      * example:
228      *
229      * parameters:
230      *      $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'"
231      *      $d   = ' '
232      *      $e1  = '('
233      *      $e2  = ')'
234      *
235      * would return an array:
236      *      array('(age < 20 AND age > 18)',
237      *            'name',
238      *            'LIKE',
239      *            'John Doe')
240      *
241      * @param string $str
242      * @param string $d         the delimeter which explodes the string
243      * @param string $e1        the first bracket, usually '('
244      * @param string $e2        the second bracket, usually ')'
245      *
246      * @return array
247      */
248     public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
249     {
250         if ($d == ' ') {
251             $d = array(' ', '\s');
252         }
253         if (is_array($d)) {
254             $d = array_map('preg_quote', $d);
255
256             if (in_array(' ', $d)) {
257                 $d[] = '\s';
258             }
259
260             $split = '#(' . implode('|', $d) . ')#';
261
262             $str = preg_split($split, $str);
263             $d = stripslashes($d[0]);
264         } else {
265             $str = explode($d, $str);
266         }
267
268         $i = 0;
269         $term = array();
270
271         foreach ($str as $key => $val) {
272             if (empty($term[$i])) {
273                 $term[$i] = trim($val);
274
275                 $s1 = substr_count($term[$i], $e1);
276                 $s2 = substr_count($term[$i], $e2);
277
278                 if (strpos($term[$i], '(') !== false) {
279                     if ($s1 == $s2) {
280                         $i++;
281                     }
282                 } else {
283                     if ( ! (substr_count($term[$i], "'") & 1) &&
284                          ! (substr_count($term[$i], "\"") & 1)) {
285                         $i++;
286                     }
287                 }
288             } else {
289                 $term[$i] .= $d . trim($val);
290                 $c1 = substr_count($term[$i], $e1);
291                 $c2 = substr_count($term[$i], $e2);
292
293                 if (strpos($term[$i], '(') !== false) {
294                     if ($c1 == $c2) {
295                         $i++;
296                     }
297                 } else {
298                     if ( ! (substr_count($term[$i], "'") & 1) &&
299                          ! (substr_count($term[$i], "\"") & 1)) {
300                         $i++;
301                     }
302                 }
303             }
304         }
305         return $term;
306     }
307
308     /**
309      * clauseExplode
310      *
311      * explodes a string into array using custom brackets and
312      * quote delimeters
313      *
314      *
315      * example:
316      *
317      * parameters:
318      *      $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'"
319      *      $d   = ' '
320      *      $e1  = '('
321      *      $e2  = ')'
322      *
323      * would return an array:
324      *      array('(age < 20 AND age > 18)',
325      *            'name',
326      *            'LIKE',
327      *            'John Doe')
328      *
329      * @param string $str
330      * @param string $d         the delimeter which explodes the string
331      * @param string $e1        the first bracket, usually '('
332      * @param string $e2        the second bracket, usually ')'
333      *
334      * @return array
335      */
336     public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')')
337     {
338         if (is_array($d)) {
339             $d = array_map('preg_quote', $d);
340
341             if (in_array(' ', $d)) {
342                 $d[] = '\s';
343             }
344
345             $split = '#(' . implode('|', $d) . ')#';
346
347             $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE);
348         }
349
350         $i = 0;
351         $term = array();
352
353         foreach ($str as $key => $val) {
354             if ($key & 1) {
355                 if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) {
356                     $term[($i - 1)] = array($term[($i - 1)], $val);
357                 }
358                 continue;
359             }
360             if (empty($term[$i])) {
361                 $term[$i] = $val;
362             } else {
363                 $term[$i] .= $str[($key - 1)] . $val;
364             }
365
366             $c1 = substr_count($term[$i], $e1);
367             $c2 = substr_count($term[$i], $e2);
368
369             if (strpos($term[$i], '(') !== false) {
370                 if ($c1 == $c2) {
371                     $i++;
372                 }
373             } else {
374                 if ( ! (substr_count($term[$i], "'") & 1) &&
375                      ! (substr_count($term[$i], "\"") & 1)) {
376                     $i++;
377                 }
378             }
379         }
380         $term[$i - 1] = array($term[$i - 1], '');
381
382         return $term;
383     }
384 }