# 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.org>.
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.org
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 | }