wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
class.StringQuery.php
Go to the documentation of this file.
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2014 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of any of the following licenses
7  * at your choice:
8  *
9  * - GNU Lesser General Public License (LGPL)
10  * http://www.gnu.org/licenses/lgpl.html
11  * - Eclipse Public License (EPL)
12  * http://www.eclipse.org/org/documents/epl-v10.php
13  *
14  * See the license.txt file distributed with this work for
15  * additional information.
16  *
17  * $Id: class.StringQuery.php 1462 2014-02-04 23:52:27Z iherwig $
18  */
19 require_once(BASE."wcmf/lib/core/class.WCMFException.php");
20 require_once(BASE."wcmf/lib/util/class.Message.php");
21 require_once(BASE."wcmf/lib/persistence/class.PersistenceFacade.php");
22 require_once(BASE."wcmf/lib/persistence/class.ObjectQuery.php");
23 require_once(BASE."wcmf/lib/model/class.NodeUtil.php");
24 require_once(BASE."wcmf/lib/util/class.StringUtil.php");
25 
26 /**
27  * @class StringQuery
28  * @ingroup Persistence
29  * @brief StringQuery executes queries from a string representation. Queries are
30  * constructed like WHERE clauses in sql, except that foreign key relations between the
31  * different types are not necessary. Attributes have to be defined with the appropriate
32  * type prepended, e.g. Author.name instead of name.
33  *
34  * The following example shows the usage:
35  *
36  * @code
37  * $queryStr = "Author.name LIKE '%ingo%' AND (Recipe.name LIKE '%Salat%' OR Recipe.portions = 4)";
38  * $query = &PersistenceFacade::createStringQuery();
39  * $authorOIDs = $query->execute('Author', $queryStr, false);
40  * @endcode
41  *
42  * @author ingo herwig <ingo@wemove.com>
43  */
45 {
46  var $_typeNode = null;
47  var $_queryString = '';
48  var $_query = '';
49 
50  /**
51  * Execute the query
52  * @param type The type to search for.
53  * @param queryString The query definition string
54  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to load (except BUILDDEPTH_REQUIRED)
55  * or false if only oids should be returned
56  * @param orderby An array holding names of attributes to ORDER by (maybe null). [default: null]
57  * @param pagingInfo A reference paging info instance (maybe null). [default: null]
58  * @param attribs An array of attributes to load (null to load all, if buildDepth != false). [default: null]
59  * @return A list of objects that match the given conditions or a list of oids
60  */
61  function execute($type, $queryString, $buildDepth, $orderby=null, &$pagingInfo, $attribs=null)
62  {
64  {
65  WCMFException::throwEx("Cannot search for unkown type '".$type."'.", __FILE__, __LINE__);
66  return $result;
67  }
68  $this->_queryString = $queryString;
69 
70  // create type node
71  $persistenceFacade = &PersistenceFacade::getInstance();
72  $this->_typeNode = &$persistenceFacade->create($type, BUILDDEPTH_SINGLE);
73  $mapper = &ObjectQuery::getMapper($this->_typeNode);
74  if ($mapper == null)
75  return array();
76 
77  // build the query
78  $this->_query = $this->buildQuery($buildDepth, $attribs);
79 
80  return ObjectQuery::executeString($type, $this->_query, $buildDepth, $orderby, $pagingInfo, $attribs);
81  }
82  /**
83  * Get the used query
84  * @note The query must be executed once to get a result
85  * @param buildDepth @see StringQuery::execute() [default: BUILDDEPTH_SINGLE]
86  * @param attribs An array of attributes to load (null to load all, if buildDepth != false). [default: null]
87  * @return The sql query string
88  */
89  function toString($buildDepth=BUILDDEPTH_SINGLE, $attribs=null)
90  {
91  if ($this->_typeNode == null)
92  WCMFException::throwEx(Message::get("StringQuery must be executed once before getting a string representation."), __FILE__, __LINE__);
93  else
94  return $this->buildQuery($buildDepth, $attribs);
95  }
96  /**
97  * Build the query
98  * @param buildDepth @see StringQuery::execute()
99  * @param attribs An array of attributes to load (null to load all, if buildDepth != false). [default: null]
100  * @return The sql query string
101  */
102  function buildQuery($buildDepth, $attribs=null)
103  {
104  $mapper = &ObjectQuery::getMapper($this->_typeNode);
105  if ($mapper == null)
106  return;
107 
108  // initialize query parts
109  $attributeStr = '';
110  $tableArray = array($table);
111  $relationArray = array();
112  $conditionStr = '';
113 
114  // create attribute string (use the default select from the mapper, since we are only interested in the attributes)
115  $tablename = ObjectQuery::getTableName($this->_typeNode);
116  if ($buildDepth === false)
117  $attribs = array();
118  $select = $mapper->getSelectSQL('', null, $attribs, true);
119  $attributeStr = $select['attributeStr'];
120 
121  // create condition string from query string
122  // tokenize by whitespace and operators
123  $queryString = $this->_queryString;
124  $tokens = StringUtil::splitQuoted($queryString, "/[\s=<>()!]+/", "'", true);
125 
126  $operators = array('and', 'or', 'not', 'like', 'is', 'null');
127  $typeArray = array();
128  foreach ($tokens as $token)
129  {
130  if (strlen($token) > 0)
131  {
132  if (!in_array(strtolower($token), $operators))
133  {
134  // three possibilities left: token is
135  // 1. type or attribute (not allowed)
136  // 2. type.attribute
137  // 3. searchterm
138  if (!preg_match('/^\'|^"|^[0-9]/', $token))
139  {
140  // token is no searchterm (does not start with a quote or a number)
141  $token = str_replace('`', '', $token);
142  $pos = strpos($token, '.');
143  if ($pos > 0)
144  {
145  // token is type.attribute
146  $type = substr($token, 0, $pos);
147  $attribute = substr($token, $pos+1, strlen($token));
149  {
150  list($table, $column) = StringQuery::mapToDatabase($type, $attribute);
151  $queryString = str_replace($type.'.'.$attribute, $table.'.'.$column, $queryString);
152 
153  if ($type != $this->_typeNode->getType())
154  {
155  array_push($typeArray, $type);
156  array_push($tableArray, $table);
157  }
158  }
159  else
160  WCMFException::throwEx("The type '".$type."' is not known.", __FILE__, __LINE__);
161  }
162  else
163  WCMFException::throwEx("Please specify the type to that the attribute '".$token."' belongs: e.g. Author.name.", __FILE__, __LINE__);
164  }
165  }
166  }
167  }
168 
169  // get relation conditions
170  $typeArray = array_unique($typeArray);
171  foreach ($typeArray as $type)
172  {
173  if ($type != $this->_typeNode->getType())
174  {
175  // check if this->_typeNode is connected with type via a parent relation
176  $parents = NodeUtil::getConnectionToAncestor($this->_typeNode, $type);
177  if ($parents != null && sizeof($parents) > 0)
178  {
179  array_push($parents, $this->_typeNode);
180  for ($i=0; $i<sizeof($parents)-1; $i++)
181  {
182  $relationStr = ObjectQuery::getRelationCondition($parents[$i], $parents[$i+1]);
183  array_push($relationArray, $relationStr);
184  }
185  }
186  else
187  {
188  // check if this->_typeNode is connected with type via a children relation
189  $children = NodeUtil::getConnectionToDescendant($this->_typeNode, $type);
190  if ($children != null && sizeof($children) > 0)
191  {
192  array_unshift($children, $this->_typeNode);
193  for ($i=0; $i<sizeof($children)-1; $i++)
194  {
195  $relationStr = ObjectQuery::getRelationCondition($children[$i], $children[$i+1]);
196  array_push($relationArray, $relationStr);
197  }
198  }
199  else
200  WCMFException::throwEx("There is no connection between '".$this->_typeNode->getType()."' and '".$type."'.", __FILE__, __LINE__);
201  }
202  }
203  }
204 
205  // add table array to table string from mapper
206  $tableStr = $select['tableStr'];
207  foreach ($tableArray as $table)
208  {
209  if (preg_match('/\b'.$table.'\b/', $tableStr) == 0)
210  $tableStr = $table.", ".$tableStr;
211  }
212 
213  // assemble the final query
214  $query = 'SELECT DISTINCT '.$attributeStr.' FROM '.$tableStr;
215  if (strlen($queryString) > 0)
216  $query .= ' WHERE '.$queryString;
217  else
218  $query .= ' WHERE 1';
219  if (sizeof($relationArray) > 0)
220  $query .= ' AND '.join(' AND ', array_unique($relationArray));
221 
222  return $query;
223  }
224  /**
225  * Map a application type and value name to the appropriate database names
226  * @param type The type to map
227  * @param valueName The name of the value to map
228  * @return An array with the table and column name or null if no mapper is found
229  */
230  function mapToDatabase($type, $valueName)
231  {
232  $persistenceFacade = &PersistenceFacade::getInstance();
233  $mapper = &ObjectQuery::getMapper($persistenceFacade->create($type, BUILDDEPTH_SINGLE));
234  if ($mapper != null)
235  {
236  $table = $mapper->getTableName();
237  $column = $mapper->getColumnName($valueName);
238  return array($table, $column);
239  }
240  return null;
241  }
242 }
243 ?>
executeString($type, $query, $buildDepth, $orderby=null, &$pagingInfo)
get($message, $parameters=null, $domain='', $lang='')
getRelationCondition(&$parentTpl, &$childTpl)
throwEx($message, $file='', $line='')
getConnectionToAncestor(&$tplNode, $ancestorType, $nodes=null)
& getMapper(&$node)
buildQuery($buildDepth, $attribs=null)
execute($type, $queryString, $buildDepth, $orderby=null, &$pagingInfo, $attribs=null)
mapToDatabase($type, $valueName)
getTableName(&$tpl, $asAliasString=false)
splitQuoted($str, $delim='//', $quoteChr='"', $preserve=false)
StringQuery executes queries from a string representation. Queries are constructed like WHERE clauses...
getConnectionToDescendant(&$tplNode, $descendantType, $nodes=null)
const BUILDDEPTH_SINGLE
toString($buildDepth=BUILDDEPTH_SINGLE, $attribs=null)