Coverage for Doctrine_RawSql

Back to coverage report

1 <?php
2 /*
3  *  $Id: RawSql.php 2963 2007-10-21 06:23:59Z Jonathan.Wage $
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 Doctrine::autoload('Doctrine_Query_Abstract');
22 /**
23  * Doctrine_RawSql
24  *
25  * @package     Doctrine
26  * @subpackage  RawSql
27  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
28  * @link        www.phpdoctrine.com
29  * @since       1.0
30  * @version     $Revision: 2963 $
31  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
32  */
33 class Doctrine_RawSql extends Doctrine_Query_Abstract
34 {
35     /**
36      * @var array $fields
37      */
38     private $fields = array();
39
40     /**
41      * parseQueryPart
42      * parses given query part
43      *
44      * @param string $queryPartName     the name of the query part
45      * @param string $queryPart         query part to be parsed
46      * @param boolean $append           whether or not to append the query part to its stack
47      *                                  if false is given, this method will overwrite 
48      *                                  the given query part stack with $queryPart
49      * @return Doctrine_Query           this object
50      */
51     public function parseQueryPart($queryPartName, $queryPart, $append = false) 
52     {
53         if ($queryPartName == 'select') {
54             preg_match_all('/{([^}{]*)}/U', $queryPart, $m);
55
56             $this->fields = $m[1];
57             $this->parts['select'] = array();
58         } else {
59             if ( ! $append) {
60                 $this->parts[$queryPartName] = array($queryPart);
61             } else {
62                 $this->parts[$queryPartName][] = $queryPart;
63             }
64         }
65         return $this;
66     }
67
68     /**
69      * parseQuery
70      * parses an sql query and adds the parts to internal array
71      *
72      * @param string $query         query to be parsed
73      * @return Doctrine_RawSql      this object
74      */
75     public function parseQuery($query)
76     {
77         preg_match_all('/{([^}{]*)}/U', $query, $m);
78
79         $this->fields = $m[1];
80         $this->clear();
81
82         $e = Doctrine_Tokenizer::sqlExplode($query,' ');
83
84         foreach ($e as $k => $part) {
85             $low = strtolower($part);
86             switch (strtolower($part)) {
87                 case 'select':
88                 case 'from':
89                 case 'where':
90                 case 'limit':
91                 case 'offset':
92                 case 'having':
93                     $p = $low;
94                     if ( ! isset($parts[$low])) {
95                         $parts[$low] = array();
96                     }
97                     break;
98                 case 'order':
99                 case 'group':
100                     $i = ($k + 1);
101                     if (isset($e[$i]) && strtolower($e[$i]) === 'by') {
102                         $p = $low;
103                         $p .= 'by';
104                         $parts[$low . 'by'] = array();
105
106                     } else {
107                         $parts[$p][] = $part;
108                     }
109                     break;
110                 case 'by':
111                     continue;
112                 default:
113                     if ( ! isset($parts[$p][0])) {
114                         $parts[$p][0] = $part;
115                     } else {
116                         $parts[$p][0] .= ' '.$part;
117                     }
118             }
119         }
120
121         $this->parts = $parts;
122         $this->parts['select'] = array();
123
124         return $this;
125     }
126
127     /**
128      * getQuery
129      * builds the sql query from the given query parts
130      *
131      * @return string       the built sql query
132      */
133     public function getQuery()
134     {
135         $select = array();
136
137         foreach ($this->fields as $field) {
138             $e = explode('.', $field);
139             if ( ! isset($e[1])) {
140                 throw new Doctrine_RawSql_Exception('All selected fields in Sql query must be in format tableAlias.fieldName');
141             }
142             // try to auto-add component
143             if ( ! $this->hasTableAlias($e[0])) {
144                 try {
145                     $this->addComponent($e[0], ucwords($e[0]));
146                 } catch(Doctrine_Exception $exception) {
147                     throw new Doctrine_RawSql_Exception('The associated component for table alias ' . $e[0] . ' couldn\'t be found.');
148                 }
149             }
150
151             $componentAlias = $this->getComponentAlias($e[0]);
152             
153             if ($e[1] == '*') {
154                 foreach ($this->_aliasMap[$componentAlias]['table']->getColumnNames() as $name) {
155                     $field = $e[0] . '.' . $name;
156
157                     $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $name;
158                 }
159             } else {
160                 $field = $e[0] . '.' . $e[1];
161                 $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $e[1];
162             }
163         }
164
165         // force-add all primary key fields
166
167         foreach ($this->getTableAliases() as $tableAlias => $componentAlias) {
168             $map = $this->_aliasMap[$componentAlias];
169
170             foreach ((array) $map['table']->getIdentifier() as $key) {
171                 $field = $tableAlias . '.' . $key;
172
173                 if ( ! isset($this->parts['select'][$field])) {
174                     $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key;
175                 }
176             }
177         }
178         
179         // first add the fields of the root component
180         reset($this->_aliasMap);
181         $componentAlias = key($this->_aliasMap);
182
183         $q = 'SELECT ' . implode(', ', $select[$componentAlias]);
184         unset($select[$componentAlias]);
185
186         foreach ($select as $component => $fields) {
187             if ( ! empty($fields)) {
188                 $q .= ', ' . implode(', ', $fields);
189             }
190         }
191
192         $string = $this->applyInheritance();
193         if ( ! empty($string)) {
194             $this->parts['where'][] = $string;
195         }
196         $copy = $this->parts;
197         unset($copy['select']);
198
199         $q .= ( ! empty($this->parts['from']))?    ' FROM '     . implode(' ', $this->parts['from']) : '';
200         $q .= ( ! empty($this->parts['where']))?   ' WHERE '    . implode(' AND ', $this->parts['where']) : '';
201         $q .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ', $this->parts['groupby']) : '';
202         $q .= ( ! empty($this->parts['having']))?  ' HAVING '   . implode(' AND ', $this->parts['having']) : '';
203         $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']) : '';
204         $q .= ( ! empty($this->parts['limit']))?   ' LIMIT ' . implode(' ', $this->parts['limit']) : '';
205         $q .= ( ! empty($this->parts['offset']))?  ' OFFSET ' . implode(' ', $this->parts['offset']) : '';
206
207         if ( ! empty($string)) {
208             array_pop($this->parts['where']);
209         }
210         return $q;
211     }
212
213     /**
214      * getFields
215      * returns the fields associated with this parser
216      *
217      * @return array    all the fields associated with this parser
218      */
219     public function getFields()
220     {
221         return $this->fields;
222     }
223
224     /**
225      * addComponent
226      *
227      * @param string $tableAlias
228      * @param string $componentName
229      * @return Doctrine_RawSql
230      */
231     public function addComponent($tableAlias, $path)
232     {
233         $tmp           = explode(' ', $path);
234         $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
235
236         $e = explode('.', $tmp[0]);
237
238         $fullPath = $tmp[0];
239         $fullLength = strlen($fullPath);
240
241         $table = null;
242
243         $currPath = '';
244
245         if (isset($this->_aliasMap[$e[0]])) {
246             $table = $this->_aliasMap[$e[0]]['table'];
247
248             $currPath = $parent = array_shift($e);
249         }
250
251         foreach ($e as $k => $component) {
252             // get length of the previous path
253             $length = strlen($currPath);
254
255             // build the current component path
256             $currPath = ($currPath) ? $currPath . '.' . $component : $component;
257
258             $delimeter = substr($fullPath, $length, 1);
259
260             // if an alias is not given use the current path as an alias identifier
261             if (strlen($currPath) === $fullLength && isset($originalAlias)) {
262                 $componentAlias = $originalAlias;
263             } else {
264                 $componentAlias = $currPath;
265             }
266             if ( ! isset($table)) {
267                 $conn = Doctrine_Manager::getInstance()
268                         ->getConnectionForComponent($component);
269                         
270                 $table = $conn->getTable($component);
271                 $this->_aliasMap[$componentAlias] = array('table' => $table);
272             } else {
273                 $relation = $table->getRelation($component);
274
275                 $this->_aliasMap[$componentAlias] = array('table'    => $relation->getTable(),
276                                                           'parent'   => $parent,
277                                                           'relation' => $relation);
278             }
279             $this->addTableAlias($tableAlias, $componentAlias);
280
281             $parent = $currPath;
282         }
283
284         return $this;
285     }
286 }