ObjectQuery.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2015 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\model;
12 
28 
29 /**
30  * ObjectQuery implements a template based object query. This class provides the
31  * user with object templates on which query conditions may be set. Object templates
32  * are Node instances whose attribute values are used as conditions on the
33  * appropriate attributes. A value maybe a scalar or a Criteria instance. For
34  * example $authorTpl->setValue("name", Criteria::forValue("LIKE", "%ingo%") means searching
35  * for authors whose name contains 'ingo'. If only a scalar is given LIKE '%...%' is assumed.
36  *
37  * A value condition of a template is joined with the preceeding conditions using the combine
38  * operator (Criteria::OPERATOR_AND, Criteria::OPERATOR_OR) given in the Criteria assigned to
39  * the template value.
40  * The set of conditions of a template is preceded by the operator (Criteria::OPERATOR_AND,
41  * Criteria::OPERATOR_OR) given in the ObjectQuery::PROPERTY_COMBINE_OPERATOR property (default:
42  * Criteria::OPERATOR_AND) of the template
43  * (see ObjectQuery::getObjectTemplate()).
44  *
45  * Multiple conditions for one value are built using different object templates of the
46  * same type. Conditions sets of different object templates are grouped with brackets if
47  * they are passed to ObjectQuery::makeGroup().
48  *
49  * @note: If there are two object templates of the same type as the query type linked in
50  * a parent child relation, than the nodes that are selected depend on the attributes of
51  * the first object template that is received by ObjectQuery::getObjectTemplate.
52  *
53  * @note: The query does not search in objects, that are created inside the current transaction.
54  *
55  * The following example shows the usage:
56  *
57  * @code
58  * // The code builds the following query condition:
59  * // WHERE (Author.name LIKE '%ingo%' AND Author.email LIKE '%wemove%') OR (Author.name LIKE '%herwig%') AND
60  * // (Recipe.created >= '2004-01-01') AND (Recipe.created < '2005-01-01') AND ((Recipe.name LIKE '%Salat%') OR (Recipe.portions = 4))
61  *
62  * $query = new ObjectQuery('Author');
63  *
64  * // (Author.name LIKE '%ingo%' AND Author.email LIKE '%wemove%')
65  * $authorTpl1 = $query->getObjectTemplate('Author');
66  * $authorTpl1->setValue("name", "ingo");
67  * $authorTpl1->setValue("email", "LIKE '%wemove%'");
68  *
69  * // OR Author.name LIKE '%herwig%'
70  * $authorTpl2 = $query->getObjectTemplate('Author', null, Criteria::OPERATOR_OR);
71  * $authorTpl2->setValue("name", "herwig");
72  *
73  * // Recipe.created >= '2004-01-01' AND Recipe.created < '2005-01-01'
74  * $recipeTpl1 = $query->getObjectTemplate('Recipe');
75  * $recipeTpl1->setValue("created", ">= '2004-01-01'");
76  * $recipeTpl2 = $query->getObjectTemplate('Recipe');
77  * $recipeTpl2->setValue("created", "< '2005-01-01'");
78  *
79  * // AND (Recipe.name LIKE '%Salat%' OR Recipe.portions = 4)
80  * // could have be built using one template, but this demonstrates the usage
81  * // of the ObjectQuery::makeGroup() method
82  * $recipeTpl3 = $query->getObjectTemplate('Recipe');
83  * $recipeTpl3->setValue("name", "Salat");
84  * $recipeTpl4 = $query->getObjectTemplate('Recipe', null, Criteria::OPERATOR_OR);
85  * $recipeTpl4->setValue("portions", "= 4");
86  * $query->makeGroup(array($recipeTpl3, $recipeTpl4), Criteria::OPERATOR_AND);
87  *
88  * $authorTpl1->addNode($recipeTpl1, 'Recipe');
89  * $authorTpl1->addNode($recipeTpl2, 'Recipe');
90  * $authorTpl1->addNode($recipeTpl3, 'Recipe');
91  * $authorTpl1->addNode($recipeTpl4, 'Recipe');
92  * $authorList = $query->execute(BuildDepth::SINGLE);
93  * @endcode
94  *
95  * @note There are some limitations when using this class:
96  * - This class works only with Nodes as PersistentObjects
97  * - This class only works for Nodes mapped by RDBMapper subclasses.
98  * - All objects have to reside in the same datastore (the connection is taken from the first mapper)
99  * - Since the query values are set together with the operator in a single string,
100  * they must be converted to data store format already
101  *
102  * @author ingo herwig <ingo@wemove.com>
103  */
104 class ObjectQuery extends AbstractQuery {
105 
106  const PROPERTY_COMBINE_OPERATOR = "object_query_combine_operator";
107  const PROPERTY_TABLE_NAME = "object_query_table_name";
108  const PROPERTY_INITIAL_OID = "object_query_initial_oid";
109 
110  private $_id = '';
111  private $_typeNode = null;
112  private $_isTypeNodeInQuery = false;
113  private $_rootNodes = array();
114  private $_conditions = array();
115  private $_groups = array();
116  private $_groupedOIDs = array();
117  private $_processedNodes = array();
118  private $_involvedTypes = array();
119  private $_aliasCounter = 1;
120  private $_observedObjects = array();
121  private $_bindOrder = array();
122 
123  /**
124  * Constructor.
125  * @param $type The type to search for
126  * @param $queryId Identifier for the query cache (maybe null to prevent caching) (default: _null_)
127  */
128  public function __construct($type, $queryId=SelectStatement::NO_CACHE) {
129  // don't use PersistenceFacade::create, because template instances must be transient
130  $mapper = $this->getMapper($type);
131  $this->_typeNode = $mapper->create($type, BuildDepth::SINGLE);
132  $this->_rootNodes[] = $this->_typeNode;
133  $this->_id = $queryId == null ? SelectStatement::NO_CACHE : $queryId;
134  ObjectFactory::getInstance('eventManager')->addListener(ValueChangeEvent::NAME,
135  array($this, 'valueChanged'));
136  }
137 
138  /**
139  * Desctructor.
140  */
141  public function __destruct() {
142  ObjectFactory::getInstance('eventManager')->removeListener(ValueChangeEvent::NAME,
143  array($this, 'valueChanged'));
144  }
145 
146  /**
147  * Get the query id
148  * @return String
149  */
150  public function getId() {
151  return $this->_id;
152  }
153 
154  /**
155  * Get an object template for a given type.
156  * @param $type The type to query for
157  * @param $alias An alias name to be used in the query. if null, use the default name (default: _null_)
158  * @param $combineOperator One of the Criteria::OPERATOR constants that precedes
159  * the conditions described in the template (default: _Criteria::OPERATOR_AND_)
160  * @return Node
161  */
162  public function getObjectTemplate($type, $alias=null, $combineOperator=Criteria::OPERATOR_AND) {
163  $template = null;
164 
165  // use the typeNode, the first time a node template of the query type is requested
166  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
167  $fqType = $persistenceFacade->getFullyQualifiedType($type);
168  if ($fqType == $this->_typeNode->getType() && !$this->_isTypeNodeInQuery) {
169  $template = $this->_typeNode;
170  $this->_isTypeNodeInQuery = true;
171  // the typeNode is contained already in the rootNodes array
172  }
173  else {
174  // don't use PersistenceFacade::create, because template instances must be transient
175  $mapper = $this->getMapper($fqType);
176  $template = $mapper->create($fqType, BuildDepth::SINGLE);
177  $this->_rootNodes[] = $template;
178  }
179  $template->setProperty(self::PROPERTY_COMBINE_OPERATOR, $combineOperator);
180  if ($alias != null) {
181  $template->setProperty(self::PROPERTY_TABLE_NAME, $alias);
182  }
183  $initialOid = $template->getOID()->__toString();
184  $template->setProperty(self::PROPERTY_INITIAL_OID, $initialOid);
185  $this->_observedObjects[$initialOid] = $template;
186  return $template;
187  }
188 
189  /**
190  * Register an object template at the query.
191  * @param $template A reference to the template to register (must be an instance of PersistentObject)
192  * @param $alias An alias name to be used in the query. if null, use the default name (default: _null_)
193  * @param $combineOperator One of the Criteria::OPERATOR constants that precedes
194  * the conditions described in the template (default: Criteria::OPERATOR_AND)
195  */
196  public function registerObjectTemplate(Node $template, $alias=null, $combineOperator=Criteria::OPERATOR_AND) {
197  if ($template != null) {
198  $initialOid = $template->getOID()->__toString();
199  $template->setProperty(self::PROPERTY_INITIAL_OID, $initialOid);
200  $this->_observedObjects[$initialOid] = $template;
201 
202  // call the setters for all attributes in order to register them in the query
203  $template->copyValues($template);
204 
205  $template->setProperty(self::PROPERTY_COMBINE_OPERATOR, $combineOperator);
206  if ($alias != null) {
207  $template->setProperty(self::PROPERTY_TABLE_NAME, $alias);
208  }
209 
210  // replace the typeNode, the first time a node template of the query type is registered
211  if ($template->getType() == $this->_typeNode->getType() && !$this->_isTypeNodeInQuery) {
212  $newRootNodes = array($template);
213  foreach($this->_rootNodes as $node) {
214  if ($node != $this->_typeNode) {
215  $newRootNodes[] = $node;
216  }
217  }
218  $this->_rootNodes = $newRootNodes;
219  $this->_isTypeNodeInQuery = true;
220  }
221  else {
222  $this->_rootNodes[] = $template;
223  }
224  }
225  }
226 
227  /**
228  * Group different templates together to realize brackets in the query.
229  * @note Grouped templates will be ignored, when iterating over the object tree and appended at the end.
230  * @param $templates An array of references to the templates contained in the group
231  * @param $combineOperator One of the Criteria::OPERATOR constants that precedes the group (default: _Criteria::OPERATOR_AND_)
232  */
233  public function makeGroup($templates, $combineOperator=Criteria::OPERATOR_AND) {
234  $this->_groups[] = array('tpls' => $templates, self::PROPERTY_COMBINE_OPERATOR => $combineOperator);
235  // store grouped nodes in an extra array to separate them from the others
236  for ($i=0; $i<sizeof($templates); $i++) {
237  if ($templates[$i] != null) {
238  $this->_groupedOIDs[] = $templates[$i]->getOID();
239  }
240  else {
241  throw new IllegalArgumentException("Null value found in group");
242  }
243  }
244  }
245 
246  /**
247  * Get the condition part of the query. This is especially useful to
248  * build a StringQuery from the query objects.
249  * @return String
250  */
251  public function getQueryCondition() {
252  $query = $this->getQueryString();
253  $tmp = preg_split("/ WHERE /i", $query);
254  if (sizeof($tmp) > 1) {
255  $tmp = preg_split("/ ORDER /i", $tmp[1]);
256  return $tmp[0];
257  }
258  return '';
259  }
260 
261  /**
262  * @see AbstractQuery::getQueryType()
263  */
264  protected function getQueryType() {
265  return $this->_typeNode->getType();
266  }
267 
268  /**
269  * @see AbstractQuery::buildQuery()
270  */
271  protected function buildQuery($orderby=null, PagingInfo $pagingInfo=null) {
272  $type = $this->_typeNode->getType();
273  $mapper = self::getMapper($type);
274  $this->_involvedTypes[$type] = true;
275 
276  // create the attribute string (use the default select from the mapper,
277  // since we are only interested in the attributes)
278  $tableName = self::processTableName($this->_typeNode);
279  $selectStmt = $mapper->getSelectSQL(null, $tableName['alias'], array(), $pagingInfo, $this->getId());
280  if (!$selectStmt->isCached()) {
281  // initialize the statement
282  $selectStmt->distinct(true);
283 
284  // process all root nodes except for grouped nodes
285  foreach ($this->_rootNodes as $curNode) {
286  if (!in_array($curNode->getOID(), $this->_groupedOIDs)) {
287  $this->processObjectTemplate($curNode, $selectStmt);
288  }
289  }
290 
291  // process groups
292  for ($i=0, $countI=sizeof($this->_groups); $i<$countI; $i++) {
293  $group = $this->_groups[$i];
294  $tmpSelectStmt = SelectStatement::get($mapper, $this->getId().'_g'.$i);
295  $tmpSelectStmt->from($this->_typeNode->getProperty(self::PROPERTY_TABLE_NAME));
296  for ($j=0, $countJ=sizeof($group['tpls']); $j<$countJ; $j++) {
297  $tpl = $group['tpls'][$j];
298  $this->processObjectTemplate($tpl, $tmpSelectStmt);
299  }
300  $condition = '';
301  $wherePart = $tmpSelectStmt->getPart(SelectStatement::WHERE);
302  foreach($wherePart as $where) {
303  $condition .= " ".$where;
304  }
305  $condition = trim($condition);
306  if (strlen($condition) > 0) {
307  $combineOperator = $group[self::PROPERTY_COMBINE_OPERATOR];
308  if ($combineOperator == Criteria::OPERATOR_OR) {
309  $selectStmt->orWhere($condition);
310  }
311  else {
312  $selectStmt->where($condition);
313  }
314  }
315  }
316 
317  // set orderby after all involved tables are known in order to
318  // prefix the correct table name
319  $this->processOrderBy($orderby, $selectStmt);
320 
321  // set bind order to be reused next time
322  $selectStmt->setMeta('bindOrder', $this->_bindOrder);
323  }
324 
325  // set parameters
326  $selectStmt->bind($this->getBind($this->_conditions, $selectStmt->getMeta('bindOrder')));
327 
328  // reset internal variables
329  $this->resetInternals();
330 
331  return $selectStmt;
332  }
333 
334  /**
335  * Process an object template
336  * @param $tpl The object template
337  * @param $selectStmt A SelectStatement instance
338  */
339  protected function processObjectTemplate(PersistentObject $tpl, SelectStatement $selectStmt) {
340  // avoid infinite recursion
341  $oidStr = $tpl->getOID()->__toString();
342  if (isset($this->_processedNodes[$oidStr])) {
343  return;
344  }
345 
346  $mapper = self::getMapper($tpl->getType());
347  $tableName = self::processTableName($tpl);
348  $this->_involvedTypes[$tpl->getType()] = true;
349 
350  // add condition
351  $condition = '';
352  $iter = new NodeValueIterator($tpl, false);
353  foreach($iter as $valueName => $value) {
354  // check if the value was set when building the query
355  if (isset($this->_conditions[$oidStr][$valueName])) {
356  $criterion = $this->_conditions[$oidStr][$valueName];
357  if ($criterion instanceof Criteria) {
358  $attributeDesc = $mapper->getAttribute($valueName);
359  if ($attributeDesc) {
360  // add the combine operator, if there are already other conditions
361  if (strlen($condition) > 0) {
362  $condition .= ' '.$criterion->getCombineOperator().' ';
363  }
364  // because the attributes are not selected with alias, the column name has to be used
365  $condition .= $mapper->renderCriteria($criterion, '?',
366  $tpl->getProperty(self::PROPERTY_TABLE_NAME), $attributeDesc->getColumn());
367  $this->_bindOrder[] = $this->getBindPosition($criterion, $this->_conditions);
368  }
369  }
370  }
371  }
372  if (strlen($condition) > 0) {
373  $combineOperator = $tpl->getProperty(self::PROPERTY_COMBINE_OPERATOR);
374  if ($combineOperator == Criteria::OPERATOR_OR) {
375  $selectStmt->orWhere($condition);
376  }
377  else {
378  $selectStmt->where($condition);
379  }
380  }
381 
382  // register the node as processed
383  $this->_processedNodes[$oidStr] = $tpl;
384 
385  // add relations to children (this includes also many to many relations)
386  // and process children
387  foreach ($mapper->getRelations() as $relationDescription) {
388  $children = $tpl->getValue($relationDescription->getOtherRole());
389  if ($children != null && !is_array($children)) {
390  $children = array($children);
391  }
392  for($i=0, $count=sizeof($children); $i<$count; $i++) {
393  $curChild = $children[$i];
394  if ($curChild instanceof Node) {
395  // process relations
396 
397  // don't process the relation twice (e.g. in a many to many relation, both
398  // ends are child ends)
399  if (!isset($this->_processedNodes[$curChild->getOID()->__toString()])) {
400  // don't join the tables twice
401  $childTableName = self::processTableName($curChild);
402  $fromPart = $selectStmt->getPart(SelectStatement::FROM);
403  if (!isset($fromPart[$childTableName['alias']])) {
404  $childMapper = self::getMapper($curChild->getType());
405  if ($relationDescription instanceof RDBManyToOneRelationDescription) {
406  $idAttr = $childMapper->getAttribute($relationDescription->getIdName());
407  $fkAttr = $mapper->getAttribute($relationDescription->getFkName());
408  $joinCondition = $mapper->quoteIdentifier($tpl->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
409  $mapper->quoteIdentifier($fkAttr->getColumn()).' = '.
410  $childMapper->quoteIdentifier($curChild->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
411  $childMapper->quoteIdentifier($idAttr->getColumn());
412 
413  $selectStmt->join(array($childTableName['alias'] => $childTableName['name']), $joinCondition, '');
414  }
415  elseif ($relationDescription instanceof RDBOneToManyRelationDescription) {
416  $idAttr = $mapper->getAttribute($relationDescription->getIdName());
417  $fkAttr = $childMapper->getAttribute($relationDescription->getFkName());
418  $joinCondition = $childMapper->quoteIdentifier($curChild->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
419  $childMapper->quoteIdentifier($fkAttr->getColumn()).' = '.
420  $mapper->quoteIdentifier($tpl->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
421  $mapper->quoteIdentifier($idAttr->getColumn());
422 
423  $selectStmt->join(array($childTableName['alias'] => $childTableName['name']), $joinCondition, '');
424  }
425  elseif ($relationDescription instanceof RDBManyToManyRelationDescription) {
426  $thisRelationDescription = $relationDescription->getThisEndRelation();
427  $otherRelationDescription = $relationDescription->getOtherEndRelation();
428 
429  $nmMapper = self::getMapper($thisRelationDescription->getOtherType());
430  $otherFkAttr = $nmMapper->getAttribute($otherRelationDescription->getFkName());
431  $otherIdAttr = $childMapper->getAttribute($otherRelationDescription->getIdName());
432  $thisFkAttr = $nmMapper->getAttribute($thisRelationDescription->getFkName());
433  $thisIdAttr = $mapper->getAttribute($thisRelationDescription->getIdName());
434 
435  $joinCondition1 = $nmMapper->quoteIdentifier($nmMapper->getRealTableName()).'.'.
436  $nmMapper->quoteIdentifier($thisFkAttr->getColumn()).' = '.
437  $mapper->quoteIdentifier($tpl->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
438  $mapper->quoteIdentifier($thisIdAttr->getColumn());
439  $joinCondition2 = $childMapper->quoteIdentifier($curChild->getProperty(self::PROPERTY_TABLE_NAME)).'.'.
440  $childMapper->quoteIdentifier($otherIdAttr->getColumn()).' = '.
441  $nmMapper->quoteIdentifier($nmMapper->getRealTableName()).'.'.
442  $nmMapper->quoteIdentifier($otherFkAttr->getColumn());
443 
444  $selectStmt->join($nmMapper->getRealTableName(), $joinCondition1, '');
445  $selectStmt->join(array($childTableName['alias'] => $childTableName['name']), $joinCondition2, '');
446 
447  // register the nm type
448  $this->_involvedTypes[$nmMapper->getType()] = true;
449  }
450  }
451  }
452 
453  // process child
454  if (!in_array($curChild->getOID(), $this->_groupedOIDs)) {
455  $this->processObjectTemplate($curChild, $selectStmt);
456  }
457  }
458  }
459  }
460  }
461 
462  /**
463  * Process an object template
464  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (maybe null)
465  * @param $selectStmt A SelectStatement instance
466  */
467  protected function processOrderBy($orderby, SelectStatement $selectStmt) {
468  if ($orderby) {
469  $ok = false;
470 
471  // reset current order by
472  $selectStmt->reset(SelectStatement::ORDER);
473 
474  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
475  foreach ($orderby as $curOrderBy) {
476  $orderByParts = preg_split('/ /', $curOrderBy);
477  $orderAttribute = $orderByParts[0];
478  $orderDirection = sizeof($orderByParts) > 1 ? $orderByParts[1] : 'ASC';
479  $orderType = null;
480 
481  if (strpos($orderAttribute, '.') > 0) {
482  // the type is included in the attribute
483  $orderAttributeParts = preg_split('/\./', $orderAttribute);
484  $orderAttribute = array_pop($orderAttributeParts);
485  $orderType = join('.', $orderAttributeParts);
486  $orderTypeMapper = $persistenceFacade->getMapper($orderType);
487  }
488  else {
489  // check all involved types
490  foreach (array_keys($this->_involvedTypes) as $curType) {
491  $mapper = $persistenceFacade->getMapper($curType);
492  if ($mapper->hasAttribute($orderAttribute)) {
493  $orderTypeMapper = $mapper;
494  break;
495  }
496  }
497  }
498  if ($orderTypeMapper) {
499  $orderTableName = $orderTypeMapper->getRealTableName();
500  $orderAttributeDesc = $orderTypeMapper->getAttribute($orderAttribute);
501  $orderColumnName = $orderAttributeDesc->getColumn();
502 
503  if ($orderTableName) {
504  $orderAttributeFinal = $orderTableName.'.'.$orderColumnName;
505  $selectStmt->order(array($orderAttributeFinal.' '.$orderDirection));
506  $ok = true;
507  }
508  }
509  }
510  if (!$ok) {
511  throw new UnknownFieldException($orderAttribute, "The sort field name '"+$orderAttribute+"' is unknown");
512  }
513  }
514  }
515 
516  /**
517  * Get an array of values for bind
518  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (maybe null)
519  * @param $bindOrder Array defining the order in which the conditions should be bind
520  * @return Array
521  */
522  protected function getBind($criteria, array $bindOrder) {
523  $bind = array();
524  // flatten conditions
525  $criteriaFlat = array();
526  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
527  foreach ($criteria as $key => $curCriteria) {
528  foreach ($curCriteria as $criterion) {
529  if ($criterion instanceof Criteria) {
530  $mapper = $persistenceFacade->getMapper($criterion->getType());
531  $valueName = $criterion->getAttribute();
532  $attributeDesc = $mapper->getAttribute($valueName);
533  if ($attributeDesc) {
534  $criteriaFlat[] = $criterion;
535  }
536  }
537  }
538  }
539  // get bind values in order
540  foreach ($bindOrder as $index) {
541  $bind[] = $criteriaFlat[$index]->getValue();
542  }
543  return $bind;
544  }
545 
546  protected function getBindPosition($criterion, $criteria) {
547  $i=0;
548  foreach ($criteria as $key => $curCriteria) {
549  foreach ($curCriteria as $curCriterion) {
550  if ($curCriterion instanceof Criteria) {
551  if ($curCriterion->__toString() === $criterion->__toString()) {
552  return $i;
553  }
554  $i++;
555  }
556  }
557  }
558  }
559 
560  /**
561  * Reset internal variables. Must be called after buildQuery
562  */
563  protected function resetInternals() {
564  $this->_processedNodes = array();
565  $this->_bindOrder = array();
566  $this->_involvedTypes = array();
567  $this->_aliasCounter = 1;
568  }
569 
570  /**
571  * Get the table name for the template and calculate an alias if
572  * necessary.
573  * @param $tpl The object template
574  * @return Associative array with keys 'name', 'alias'
575  */
576  protected function processTableName(Node $tpl) {
577  $mapper = self::getMapper($tpl->getType());
578  $mapperTableName = $mapper->getRealTableName();
579 
580  $tableName = $tpl->getProperty(self::PROPERTY_TABLE_NAME);
581  if ($tableName == null) {
582  $tableName = $mapperTableName;
583 
584  // if the template is the child of another node of the same type,
585  // we must use a table alias
586  $parents = $tpl->getParentsEx(null, null, $tpl->getType());
587  foreach ($parents as $curParent) {
588  $curParentTableName = $curParent->getProperty(self::PROPERTY_TABLE_NAME);
589  if ($curParentTableName == $tableName) {
590  $tableName .= '_'.($this->_aliasCounter++);
591  }
592  }
593  // set the table name for later reference
594  $tpl->setProperty(self::PROPERTY_TABLE_NAME, $tableName);
595  }
596 
597  return array('name' => $mapperTableName, 'alias' => $tableName);
598  }
599 
600  /**
601  * Listen to ValueChangeEvents
602  * @param $event ValueChangeEvent instance
603  */
604  public function valueChanged(ValueChangeEvent $event) {
605  $object = $event->getObject();
606  $name = $event->getValueName();
607  $initialOid = $object->getProperty(self::PROPERTY_INITIAL_OID);
608  if (isset($this->_observedObjects[$initialOid])) {
609  $newValue = $event->getNewValue();
610  // make a criteria from newValue and make sure that all properties are set
611  if (!($newValue instanceof Criteria)) {
612  // LIKE fallback, if the value is not a Criteria instance
613  $mapper = self::getMapper($object->getType());
614  $pkNames = $mapper->getPkNames();
615  if (!in_array($name, $pkNames)) {
616  // use like condition on any attribute, if it's a string
617  // other value changes will be ignored!
618  if (is_string($newValue)) {
619  $newValue = new Criteria($object->getType(), $name, 'LIKE', '%'.$newValue.'%');
620  }
621  }
622  else {
623  // don't search for pk names with LIKE
624  $newValue = new Criteria($object->getType(), $name, '=', $newValue);
625  }
626  }
627  else {
628  // make sure that type and name are set even if the Criteria is constructed
629  // via Criteria::forValue()
630  $newValue->setType($object->getType());
631  $newValue->setAttribute($name);
632  }
633 
634  $oid = $object->getOID()->__toString();
635  // store change in internal array to have it when constructing the query
636  if (!isset($this->_conditions[$oid])) {
637  $this->_conditions[$oid] = array();
638  }
639  $this->_conditions[$oid][$name] = $newValue;
640  }
641  }
642 }
643 ?>
getType()
Get the type of the object.
resetInternals()
Reset internal variables.
Node related interfaces and classes.
Definition: namespaces.php:26
getParentsEx(ObjectId $oid=null, $role=null, $type=null, $values=null, $properties=null, $useRegExp=true)
Get the parents that match given conditions.
Definition: Node.php:631
getOID()
Get the object id of the PersistentObject.
getObject()
Get the object whose value has changed.
makeGroup($templates, $combineOperator=Criteria::OPERATOR_AND)
Group different templates together to realize brackets in the query.
processTableName(Node $tpl)
Get the table name for the template and calculate an alias if necessary.
getValueName()
Get the name of the value that has changed.
IllegalArgumentException signals an exception in method arguments.
copyValues(PersistentObject $object, $copyPkValues=true)
NodeValueIterator is used to iterate over all persistent values of a Node (not including relations)...
getQueryString($orderby=null)
Get the query serialized to a string.
ObjectQuery implements a template based object query.
getProperty($name)
Get the value of a named property in the object.
UnknownFieldException signals an exception in a message.
static get(RDBMapper $mapper, $id=self::NO_CACHE)
Get the SelectStatement instance with the given id.
ValueChangeEvent signals a change of a value of a PersistentObject instance.
getId()
Get the query id.
processOrderBy($orderby, SelectStatement $selectStmt)
Process an object template.
Criteria defines a condition on a PersistentObject's attribute used to select specific instances...
Definition: Criteria.php:21
__construct($type, $queryId=SelectStatement::NO_CACHE)
Constructor.
static getInstance($name, $dynamicConfiguration=array())
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
processObjectTemplate(PersistentObject $tpl, SelectStatement $selectStmt)
Process an object template.
getObjectTemplate($type, $alias=null, $combineOperator=Criteria::OPERATOR_AND)
Get an object template for a given type.
getBindPosition($criterion, $criteria)
Instances of RDBManyToOneRelationDescription describe a many to one relation from 'this' end (many) t...
getQueryCondition()
Get the condition part of the query.
registerObjectTemplate(Node $template, $alias=null, $combineOperator=Criteria::OPERATOR_AND)
Register an object template at the query.
getValue($name)
Get the value of a named item.
getBind($criteria, array $bindOrder)
Get an array of values for bind.
Instances of RDBManyToManyRelationDescription describe a many to many relation from 'this' end to 'ot...
static getMapper($type)
Get the mapper for a Node and check if it is a supported one.
buildQuery($orderby=null, PagingInfo $pagingInfo=null)
AbstractQuery is the base class for all query classes.
Instances of RDBOneToManyRelationDescription describe a one to many relation from 'this' end (one) to...
valueChanged(ValueChangeEvent $event)
Listen to ValueChangeEvents.
Node adds the concept of relations to PersistentObject.
Definition: Node.php:34
PersistentObject defines the interface of all persistent objects.