NodeUnifiedRDBMapper.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2017 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\mapper;
12 
33 
34 /**
35  * NodeUnifiedRDBMapper maps Node objects to a relational database schema where each Node
36  * type has its own table.
37  * The wCMFGenerator uses this class as base class for all mappers.
38  *
39  * @author ingo herwig <ingo@wemove.com>
40  */
41 abstract class NodeUnifiedRDBMapper extends RDBMapper {
42 
43  const CACHE_KEY = 'mapper';
44 
45  private $fkRelations = null;
46 
47  /**
48  * @see RDBMapper::prepareForStorage()
49  */
50  protected function prepareForStorage(PersistentObject $object) {
51  $oldState = $object->getState();
52 
53  // set primary key values
54  $oid = $object->getOID();
55  $ids = $oid->getId();
56  $pkNames = $this->getPkNames();
57  for($i=0, $count=sizeof($pkNames); $i<$count; $i++) {
58  // foreign keys don't get a new id
59  $pkName = $pkNames[$i];
60  if (!$this->isForeignKey($pkName)) {
61  $pkValue = $ids[$i];
62  // replace dummy ids with ids provided by the database sequence
63  if (ObjectId::isDummyId($pkValue)) {
64  $nextId = $this->getNextId();
65  $object->setValue($pkName, $nextId);
66  }
67  }
68  }
69 
70  if ($oldState == PersistentObject::STATE_NEW) {
71  // set the sortkeys to the id value
72  if ($this->isSortable()) {
73  $value = join('', $object->getOID()->getId());
74  foreach ($this->getRelations() as $curRelationDesc) {
75  $sortkeyDef = $this->getSortkey($curRelationDesc->getOtherRole());
76  if ($sortkeyDef != null) {
77  $object->setValue($sortkeyDef['sortFieldName'], $value);
78  }
79  }
80  $sortkeyDef = $this->getSortkey();
81  if ($sortkeyDef != null) {
82  $object->setValue($sortkeyDef['sortFieldName'], $value);
83  }
84  }
85  }
86 
87  // handle relations
88  if ($object instanceof Node) {
89  // added nodes
90  $addedNodes = $object->getAddedNodes();
91  foreach ($addedNodes as $role => $addedNodes) {
92  $relationDesc = $this->getRelation($role);
93  // for a many to one relation, we need to update the appropriate
94  // foreign key in the object
95  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
96  // in a many to one only one parent is possible
97  // so we take the last oid
98  $parent = array_pop($addedNodes);
99  $poid = $parent->getOID();
100  if (ObjectId::isValid($poid)) {
101  // set the foreign key to the parent id value
102  $fkAttr = $this->getAttribute($relationDesc->getFkName());
103  $object->setValue($fkAttr->getName(), $poid->getFirstId());
104  }
105  }
106  elseif ($relationDesc instanceof RDBManyToManyRelationDescription) {
107  // in a many to many relation we have to create the relation object
108  // if it does not exist
109  $relatives = $object->getChildrenEx(null, $relationDesc->getOtherRole());
110  foreach ($relatives as $relative) {
111  // check if the relation already exists
112  $nmObjects = $this->loadRelationObjects(PersistentObjectProxy::fromObject($object),
113  PersistentObjectProxy::fromObject($relative), $relationDesc, true);
114  if (sizeof($nmObjects) == 0) {
115  $thisEndRelation = $relationDesc->getThisEndRelation();
116  $otherEndRelation = $relationDesc->getOtherEndRelation();
117  $nmType = $thisEndRelation->getOtherType();
118  $nmObj = $this->persistenceFacade->create($nmType);
119  // add the parent nodes to the many to many object, don't
120  // update the other side of the relation, because there may be no
121  // relation defined to the many to many object
122  $nmObj->addNode($object, $thisEndRelation->getThisRole(), true, false, false);
123  $nmObj->addNode($relative, $otherEndRelation->getOtherRole(), true, false, false);
124  }
125  }
126  }
127  }
128 
129  // deleted nodes
130  $deletedNodes = $object->getDeletedNodes();
131  foreach ($deletedNodes as $role => $oids) {
132  $relationDesc = $this->getRelation($role);
133  // for a many to one relation, we need to update the appropriate
134  // foreign key in the object
135  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
136  // in a many to one only one parent is possible
137  // so we take the last oid
138  $poid = array_pop($oids);
139  if (ObjectId::isValid($poid)) {
140  // set the foreign key to null
141  $fkAttr = $this->getAttribute($relationDesc->getFkName());
142  $object->setValue($fkAttr->getName(), null);
143  }
144  }
145  elseif ($relationDesc instanceof RDBManyToManyRelationDescription) {
146  // in a many to many relation we have to delete the relation object
147  // if it does exist
148  foreach ($oids as $relativeOid) {
149  // check if the relation exists
150  $nmObjects = $this->loadRelationObjects(PersistentObjectProxy::fromObject($object),
151  new PersistentObjectProxy($relativeOid), $relationDesc);
152  foreach ($nmObjects as $nmObj) {
153  // delete the relation
154  $nmObj->delete();
155  }
156  }
157  }
158  }
159 
160  // changed order
161  $nodeOrder = $object->getNodeOrder();
162  if ($nodeOrder != null) {
163  $containerMapper = $object->getMapper();
164  $orderedList = $nodeOrder['ordered'];
165  $movedList = $nodeOrder['moved'];
166  $movedLookup = $movedList != null ? array_reduce($movedList, function($result, $item) {
167  $result[$item->getOID()->__toString()] = true;
168  return $result;
169  }, []) : [];
170  $role = $nodeOrder['role'];
171  $defaultRelationDesc = $role != null ? $containerMapper->getRelation($role) : null;
172  for ($i=0, $count=sizeof($orderedList); $i<$count; $i++) {
173  $orderedNode = $orderedList[$i];
174 
175  // check if node is repositioned
176  if ($movedList == null || isset($movedLookup[$orderedNode->getOID()->__toString()])) {
177 
178  // determine the sortkey regarding the container object
179  $relationDesc = $defaultRelationDesc != null ? $defaultRelationDesc :
180  $object->getNodeRelation($orderedNode);
181  $sortkeyDef = $orderedNode->getMapper()->getSortkey($relationDesc->getThisRole());
182  $sortkey = $sortkeyDef['sortFieldName'];
183  $sortNode = $this->getSortableObject(PersistentObjectProxy::fromObject($object),
184  PersistentObjectProxy::fromObject($orderedNode), $relationDesc);
185 
186  // get previous sortkey value
187  $prevValue = null;
188  if ($i > 0) {
189  $prevNode = $orderedList[$i-1];
190  $relationDescPrev = $defaultRelationDesc != null ? $defaultRelationDesc :
191  $object->getNodeRelation($prevNode);
192  $sortkeyDefPrev = $prevNode->getMapper()->getSortkey($relationDescPrev->getThisRole());
193  $sortdirPrev = strtoupper($sortkeyDefPrev['sortDirection']);
194  $prevSortNode = $this->getSortableObject(PersistentObjectProxy::fromObject($object),
195  PersistentObjectProxy::fromObject($prevNode), $relationDesc);
196  $prevValue = $prevSortNode->getValue($sortkeyDefPrev['sortFieldName']);
197  }
198 
199  // get next sortkey value
200  $nextValue = null;
201  if ($i < $count-1) {
202  $nextNode = $orderedList[$i+1];
203  $relationDescNext = $defaultRelationDesc != null ? $defaultRelationDesc :
204  $object->getNodeRelation($nextNode);
205  $sortkeyDefNext = $nextNode->getMapper()->getSortkey($relationDescNext->getThisRole());
206  $sortdirNext = strtoupper($sortkeyDefNext['sortDirection']);
207  $nextSortNode = $this->getSortableObject(PersistentObjectProxy::fromObject($object),
208  PersistentObjectProxy::fromObject($nextNode), $relationDesc);
209  $nextValue = $nextSortNode->getValue($sortkeyDefNext['sortFieldName']);
210  }
211 
212  // set edge values
213  if ($prevValue == null) {
214  $prevValue = ceil($sortdirNext == 'ASC' ? $nextValue-1 : $nextValue+1);
215  }
216  if ($nextValue == null) {
217  $nextValue = ceil($sortdirPrev == 'ASC' ? $prevValue+1 : $prevValue-1);
218  }
219 
220  // set the sortkey value to the average
221  $sortNode->setValue($sortkey, ($nextValue+$prevValue)/2);
222  }
223  }
224  }
225  }
226  $object->setState($oldState);
227  }
228 
229  /**
230  * @see RDBMapper::getSelectSQL()
231  */
232  public function getSelectSQL($criteria=null, $alias=null, $attributes=null, $orderby=null, PagingInfo $pagingInfo=null, $queryId=null) {
233  // use own query id, if none is given
234  $queryId = $queryId == null ? $this->getCacheKey($alias, $attributes, $criteria, $orderby, $pagingInfo) : $queryId;
235 
236  $selectStmt = SelectStatement::get($this, $queryId);
237  if (!$selectStmt->isCached()) {
238  // initialize the statement
239 
240  // table
241  $tableName = $this->getRealTableName();
242  if ($alias != null) {
243  $selectStmt->from([$alias => $tableName]);
244  $tableName = $alias;
245  }
246  else {
247  $selectStmt->from($tableName);
248  }
249 
250  // columns
251  $this->addColumns($selectStmt, $tableName, $attributes);
252 
253  // condition
254  $parameters = $this->addCriteria($selectStmt, $criteria, $tableName);
255 
256  // order
257  $this->addOrderBy($selectStmt, $orderby, $this->getType(), $tableName, $this->getDefaultOrder());
258 
259  // limit
260  if ($pagingInfo != null) {
261  $selectStmt->limit($pagingInfo->getPageSize());
262  $selectStmt->offset($pagingInfo->getOffset());
263  }
264  }
265  else {
266  // on used statements only set parameters
267  $tableName = $alias != null ? $alias : $this->getRealTableName();
268  $parameters = $this->getParameters($criteria, $tableName);
269  }
270 
271  // set parameters
272  $selectStmt->setParameters($parameters);
273  return $selectStmt;
274  }
275 
276  /**
277  * @see RDBMapper::getRelationSelectSQL()
278  */
279  protected function getRelationSelectSQL(array $otherObjectProxies,
280  $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
281  $relationDescription = $this->getRelationImpl($otherRole, true);
282  if ($relationDescription instanceof RDBManyToOneRelationDescription) {
283  return $this->getManyToOneRelationSelectSQL($relationDescription,
284  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
285  }
286  elseif ($relationDescription instanceof RDBOneToManyRelationDescription) {
287  return $this->getOneToManyRelationSelectSQL($relationDescription,
288  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
289  }
290  elseif ($relationDescription instanceof RDBManyToManyRelationDescription) {
291  return $this->getManyToManyRelationSelectSQL($relationDescription,
292  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
293  }
294  throw new IllegalArgumentException("Unknown RelationDescription for role: ".$otherRole);
295  }
296 
297  /**
298  * Get the statement for selecting a many-to-one relation
299  * @see RDBMapper::getRelationSelectSQL()
300  */
301  protected function getManyToOneRelationSelectSQL(RelationDescription $relationDescription,
302  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
303  PagingInfo $pagingInfo=null) {
304  $thisAttr = $this->getAttribute($relationDescription->getFkName());
305  $tableName = $this->getRealTableName();
306 
307  // id parameters
308  $parameters = [];
309  $idPlaceholder = ':'.$tableName.'_'.$thisAttr->getName();
310  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
311  $dbid = $otherObjectProxies[$i]->getValue($relationDescription->getIdName());
312  if ($dbid === null) {
313  $dbid = SQLConst::NULL();
314  }
315  $parameters[$idPlaceholder.$i] = $dbid;
316  }
317 
318  // statement
319  $selectStmt = $this->getRelationStatement($thisAttr, $parameters,
320  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
321  return [$selectStmt, $relationDescription->getIdName(), $relationDescription->getFkName()];
322  }
323 
324  /**
325  * Get the statement for selecting a one-to-many relation
326  * @see RDBMapper::getRelationSelectSQL()
327  */
328  protected function getOneToManyRelationSelectSQL(RelationDescription $relationDescription,
329  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
330  PagingInfo $pagingInfo=null) {
331  $thisAttr = $this->getAttribute($relationDescription->getIdName());
332  $tableName = $this->getRealTableName();
333 
334  // id parameters
335  $parameters = [];
336  $idPlaceholder = ':'.$tableName.'_'.$thisAttr->getName();
337  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
338  $fkValue = $otherObjectProxies[$i]->getValue($relationDescription->getFkName());
339  if ($fkValue === null) {
340  $fkValue = SQLConst::NULL();
341  }
342  $parameters[$idPlaceholder.$i] = $fkValue;
343  }
344 
345  // statement
346  $selectStmt = $this->getRelationStatement($thisAttr, $parameters,
347  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
348  return [$selectStmt, $relationDescription->getFkName(), $relationDescription->getIdName()];
349 
350  }
351 
352  /**
353  * Get the select statement for a many-to-one or one-to-many relation.
354  * This method is the common part used in both relations.
355  * @see RDBMapper::getRelationSelectSQL()
356  */
357  protected function getRelationStatement($thisAttr, $parameters,
358  array $otherObjectProxies, $otherRole,
359  $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
360  $queryId = $this->getCacheKey($otherRole.sizeof($otherObjectProxies), null, $criteria, $orderby, $pagingInfo);
361  $tableName = $this->getRealTableName();
362  $selectStmt = SelectStatement::get($this, $queryId);
363  if (!$selectStmt->isCached()) {
364  // initialize the statement
365  $selectStmt->from($tableName, '');
366  $this->addColumns($selectStmt, $tableName);
367  $selectStmt->where($this->quoteIdentifier($tableName).'.'.
368  $this->quoteIdentifier($thisAttr->getName()).' IN('.join(',', array_keys($parameters)).')');
369  // order
370  $this->addOrderBy($selectStmt, $orderby, $this->getType(), $tableName, $this->getDefaultOrder($otherRole));
371  // additional conditions
372  $parameters = array_merge($parameters, $this->addCriteria($selectStmt, $criteria, $tableName));
373  // limit
374  if ($pagingInfo != null) {
375  $selectStmt->limit($pagingInfo->getPageSize());
376  $selectStmt->offset($pagingInfo->getOffset());
377  }
378  }
379  else {
380  // on used statements only set parameters
381  $parameters = array_merge($parameters, $this->getParameters($criteria, $tableName));
382  }
383 
384  // set parameters
385  $selectStmt->setParameters($parameters);
386  return $selectStmt;
387  }
388 
389  /**
390  * Get the statement for selecting a many-to-many relation
391  * @see RDBMapper::getRelationSelectSQL()
392  */
393  protected function getManyToManyRelationSelectSQL(RelationDescription $relationDescription,
394  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
395  PagingInfo $pagingInfo=null) {
396  $thisRelationDesc = $relationDescription->getThisEndRelation();
397  $otherRelationDesc = $relationDescription->getOtherEndRelation();
398  $nmMapper = $this->persistenceFacade->getMapper($thisRelationDesc->getOtherType());
399  $otherFkAttr = $nmMapper->getAttribute($otherRelationDesc->getFkName());
400  $nmTableName = $nmMapper->getRealTableName();
401 
402  // id parameters
403  $parameters = [];
404  $idPlaceholder = ':'.$nmTableName.'_'.$otherFkAttr->getName();
405  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
406  $dbid = $otherObjectProxies[$i]->getValue($thisRelationDesc->getIdName());
407  if ($dbid === null) {
408  $dbid = SQLConst::NULL();
409  }
410  $parameters[$idPlaceholder.$i] = $dbid;
411  }
412 
413  // statement
414  $queryId = $this->getCacheKey($otherRole.sizeof($otherObjectProxies), null, $criteria, $orderby, $pagingInfo);
415  $selectStmt = SelectStatement::get($this, $queryId);
416  if (!$selectStmt->isCached()) {
417  // initialize the statement
418 
419  $thisFkAttr = $nmMapper->getAttribute($thisRelationDesc->getFkName());
420  $thisIdAttr = $this->getAttribute($thisRelationDesc->getIdName());
421 
422  $tableName = $this->getRealTableName();
423  $selectStmt->from($tableName, '');
424  $this->addColumns($selectStmt, $tableName);
425  $joinCond = $nmTableName.'.'.$thisFkAttr->getName().'='.$tableName.'.'.$thisIdAttr->getName();
426  $joinColumns = [];
427  $selectStmt->where($this->quoteIdentifier($nmTableName).'.'.
428  $this->quoteIdentifier($otherFkAttr->getName()).' IN('.join(',', array_keys($parameters)).')');
429  // order (in this case we use the order of the many to many objects)
430  $nmSortDefs = $nmMapper->getDefaultOrder($otherRole);
431  $hasNmOrder = sizeof($nmSortDefs) > 0;
432  $orderType = $hasNmOrder ? $nmMapper->getType() : $this->getType();
433  $orderTable = $hasNmOrder ? $nmTableName : $tableName;
434  $defaultOrderDef = $hasNmOrder ? $nmSortDefs : $this->getDefaultOrder($otherRole);
435  $this->addOrderBy($selectStmt, $orderby, $orderType, $orderTable, $defaultOrderDef);
436  foreach($nmSortDefs as $nmSortDef) {
437  // add the sort attribute from the many to many object
438  $nmSortAttributeDesc = $nmMapper->getAttribute($nmSortDef['sortFieldName']);
439  $joinColumns[$nmSortAttributeDesc->getName()] = $nmSortAttributeDesc->getColumn();
440  }
441  // add proxy id
442  $joinColumns[self::INTERNAL_VALUE_PREFIX.'id'] = $otherFkAttr->getName();
443  $selectStmt->join($nmTableName, $joinCond, $joinColumns);
444  // additional conditions
445  $parameters = array_merge($parameters, $this->addCriteria($selectStmt, $criteria, $nmTableName));
446  // limit
447  if ($pagingInfo != null) {
448  $selectStmt->limit($pagingInfo->getPageSize(), $pagingInfo->getOffset());
449  }
450  }
451  else {
452  // on used statements only set parameters
453  $parameters = array_merge($parameters, $this->getParameters($criteria, $nmTableName));
454  }
455 
456  // set parameters
457  $selectStmt->setParameters($parameters);
458  return [$selectStmt, $thisRelationDesc->getIdName(), self::INTERNAL_VALUE_PREFIX.'id'];
459  }
460 
461  /**
462  * @see RDBMapper::getInsertSQL()
463  */
464  protected function getInsertSQL(PersistentObject $object) {
465  // get the attributes to store
466  $values = $this->convertValuesForStorage($this->getPersistentValues($object));
467 
468  // operations
469  $insertOp = new InsertOperation($this->getType(), $values);
470  $operations = [$insertOp];
471  return $operations;
472  }
473 
474  /**
475  * @see RDBMapper::getUpdateSQL()
476  */
477  protected function getUpdateSQL(PersistentObject $object) {
478  // get the attributes to store
479  $values = $this->convertValuesForStorage($this->getPersistentValues($object));
480 
481  // primary key definition
482  $pkCriteria = $this->createPKCondition($object->getOID());
483 
484  // operations
485  $updateOp = new UpdateOperation($this->getType(), $values, $pkCriteria);
486  $operations = [$updateOp];
487  return $operations;
488  }
489 
490  /**
491  * @see RDBMapper::getDeleteSQL()
492  */
493  protected function getDeleteSQL(ObjectId $oid) {
494  // primary key definition
495  $pkCriteria = $this->createPKCondition($oid);
496 
497  // operations
498  $deleteOp = new DeleteOperation($this->getType(), $pkCriteria);
499  $operations = [$deleteOp];
500  return $operations;
501  }
502 
503  /**
504  * Add the columns to a given select statement.
505  * @param $selectStmt The select statement (instance of SelectStatement)
506  * @param $tableName The table name
507  * @param $attributes Array of attribute names (optional)
508  * @return SelectStatement
509  */
510  protected function addColumns(SelectStatement $selectStmt, $tableName, $attributes=null) {
511  // columns
512  $attributeDescs = $this->getAttributes();
513  $columns = [];
514  foreach($attributeDescs as $curAttributeDesc) {
515  $name = $curAttributeDesc->getName();
516  if (($attributes == null || in_array($name, $attributes)) && $curAttributeDesc instanceof RDBAttributeDescription) {
517  $columns[$curAttributeDesc->getName()] = $curAttributeDesc->getColumn();
518  }
519  }
520  $selectStmt->columns($columns, true);
521 
522  // references
523  $selectStmt = $this->addReferences($selectStmt, $tableName);
524  return $selectStmt;
525  }
526 
527  /**
528  * Add the columns and joins to select references to a given select statement.
529  * @param $selectStmt The select statement (instance of SelectStatement)
530  * @param $tableName The name for this table (the alias, if used).
531  * @return SelectStatement
532  */
533  protected function addReferences(SelectStatement $selectStmt, $tableName) {
534  // collect all references first
535  $references = [];
536  foreach($this->getReferences() as $curReferenceDesc) {
537  $referencedType = $curReferenceDesc->getOtherType();
538  $referencedValue = $curReferenceDesc->getOtherName();
539  $relationDescs = $this->getRelationsByType($referencedType);
540  // get relation try role name if ambiguous
541  $relationDesc = sizeof($relationDescs) == 1 ? $relationDescs[0] : $this->getRelation($referencedType);
542  $otherMapper = $this->persistenceFacade->getMapper($relationDesc->getOtherType());
543  if ($otherMapper) {
544  $otherTable = $otherMapper->getRealTableName();
545  $otherAttributeDesc = $otherMapper->getAttribute($referencedValue);
546  if ($otherAttributeDesc instanceof RDBAttributeDescription) {
547  // set up the join definition if not already defined
548  if (!isset($references[$otherTable])) {
549  $references[$otherTable] = [];
550  $references[$otherTable]['attributes'] = [];
551 
552  $tableNameQ = $tableName;
553  $otherTableQ = $otherTable.'Ref';
554 
555  // determine the join condition
556  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
557  // reference from parent
558  $thisAttrNameQ = $this->getAttribute($relationDesc->getFkName())->getColumn();
559  $otherAttrNameQ = $otherMapper->getAttribute($relationDesc->getIdName())->getColumn();
560  $additionalCond = "";
561  }
562  else if ($relationDesc instanceof RDBOneToManyRelationDescription) {
563  // reference from child
564  $thisAttrNameQ = $this->getAttribute($relationDesc->getIdName())->getColumn();
565  $otherAttrNameQ = $otherMapper->getAttribute($relationDesc->getFkName())->getColumn();
566  $otherPkNames = $otherMapper->getPkNames();
567  $otherPkNameQ = $otherMapper->getAttribute($otherPkNames[0])->getColumn();
568  $additionalCond = " AND ".$otherTableQ.".".$otherPkNameQ.
569  " = (SELECT MIN(".$otherTableQ.".".$otherPkNameQ.") FROM ".$otherTableQ.
570  " WHERE ".$otherTableQ.".".$otherAttrNameQ."=".$tableNameQ.".".$thisAttrNameQ.")";
571  }
572  $joinCond = $tableNameQ.".".$thisAttrNameQ."=".$otherTableQ.".".$otherAttrNameQ;
573  if (strlen($additionalCond) > 0) {
574  $joinCond = "(".$joinCond.$additionalCond.")";
575  }
576  $references[$otherTable]['joinCond'] = $joinCond;
577  }
578 
579  // add the attributes
580  $references[$otherTable]['attributes'][$curReferenceDesc->getName()] = $otherAttributeDesc->getColumn();
581  }
582  }
583  }
584  // add references from each referenced table
585  foreach($references as $otherTable => $curReference) {
586  $selectStmt->join([$otherTable.'Ref' => $otherTable], $curReference['joinCond'],
587  $curReference['attributes'], SelectStatement::JOIN_LEFT);
588  }
589  return $selectStmt;
590  }
591 
592  /**
593  * Add the given criteria to the select statement
594  * @param $selectStmt The select statement (instance of SelectStatement)
595  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (maybe null)
596  * @param $tableName The table name
597  * @return Array of placeholder/value pairs
598  */
599  protected function addCriteria(SelectStatement $selectStmt, $criteria, $tableName) {
600  $parameters = [];
601  if ($criteria != null) {
602  foreach ($criteria as $criterion) {
603  if ($criterion instanceof Criteria) {
604  $placeholder = ':'.$tableName.'_'.$criterion->getAttribute();
605  list($criteriaCondition, $criteriaPlaceholder) =
606  $this->renderCriteria($criterion, $placeholder, $tableName);
607  $selectStmt->where($criteriaCondition, $criterion->getCombineOperator());
608  if ($criteriaPlaceholder) {
609  $value = $criterion->getValue();
610  if (is_array($criteriaPlaceholder)) {
611  $parameters = array_merge($parameters, array_combine($criteriaPlaceholder, $value));
612  }
613  else {
614  $parameters[$criteriaPlaceholder] = $value;
615  }
616  }
617  }
618  else {
619  throw new IllegalArgumentException("The select condition must be an instance of Criteria");
620  }
621  }
622  }
623  return $parameters;
624  }
625 
626  /**
627  * Add the given order to the select statement
628  * @param $selectStmt The select statement (instance of SelectStatement)
629  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (maybe null)
630  * @param $orderType The type that define the attributes in orderby (maybe null)
631  * @param $aliasName The table alias name to be used (maybe null)
632  * @param $defaultOrder The default order definition to use, if orderby is null (@see PersistenceMapper::getDefaultOrder())
633  */
634  protected function addOrderBy(SelectStatement $selectStmt, $orderby, $orderType, $aliasName, $defaultOrder) {
635  if ($orderby == null) {
636  $orderby = [];
637  // use default ordering
638  if ($defaultOrder && sizeof($defaultOrder) > 0) {
639  foreach ($defaultOrder as $orderDef) {
640  $orderby[] = $orderDef['sortFieldName']." ".$orderDef['sortDirection'];
641  $orderType = $orderDef['sortType'];
642  }
643  }
644  }
645  for ($i=0, $count=sizeof($orderby); $i<$count; $i++) {
646  $curOrderBy = $orderby[$i];
647  $orderByParts = preg_split('/ /', $curOrderBy);
648  $orderAttribute = $orderByParts[0];
649  $orderDirection = sizeof($orderByParts) > 1 ? $orderByParts[1] : 'ASC';
650  if (strpos($orderAttribute, '.') > 0) {
651  // the type is included in the attribute
652  $orderAttributeParts = preg_split('/\./', $orderAttribute);
653  $orderAttribute = array_pop($orderAttributeParts);
654  }
655  $mapper = $orderType != null ? $this->persistenceFacade->getMapper($orderType) : $this;
656  $orderAttributeDesc = $mapper->getAttribute($orderAttribute);
657  if ($orderAttributeDesc instanceof ReferenceDescription) {
658  // add the referenced column without table name
659  $mapper = $this->persistenceFacade->getMapper($orderAttributeDesc->getOtherType());
660  $orderAttributeDesc = $mapper->getAttribute($orderAttributeDesc->getOtherName());
661  $orderColumnName = $orderAttributeDesc->getColumn();
662  }
663  elseif ($orderAttributeDesc instanceof TransientAttributeDescription) {
664  // skip, because no column exists
665  continue;
666  }
667  else {
668  // add the column with table name
669  $tableName = $aliasName != null ? $aliasName : $mapper->getRealTableName();
670  $orderColumnName = $tableName.'.'.$orderAttributeDesc->getColumn();
671  }
672  $selectStmt->order([$orderColumnName.' '.$orderDirection]);
673  }
674  }
675 
676  /**
677  * Get an array of placeholder/value pairs
678  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (maybe null)
679  * @param $tableName The table name
680  * @return Array of placeholder/value pairs
681  */
682  protected function getParameters($criteria, $tableName) {
683  $parameters = [];
684  if ($criteria != null) {
685  foreach ($criteria as $criterion) {
686  if ($criterion instanceof Criteria) {
687  $placeholder = ':'.$tableName.'_'.$criterion->getAttribute();
688  list($criteriaCondition, $criteriaPlaceholder) = $this->renderCriteria($criterion, $placeholder, '', '');
689  if ($criteriaPlaceholder) {
690  $value = $criterion->getValue();
691  if (is_array($criteriaPlaceholder)) {
692  $parameters = array_merge($parameters, array_combine($criteriaPlaceholder, $value));
693  }
694  else {
695  $parameters[$criteriaPlaceholder] = $value;
696  }
697  }
698  }
699  else {
700  throw new IllegalArgumentException("The select condition must be an instance of Criteria");
701  }
702  }
703  }
704  return $parameters;
705  }
706 
707  /**
708  * Get an associative array of attribute name-value pairs to be stored for a
709  * given oject (references are not included)
710  * @param $object The PeristentObject.
711  * @return Associative array
712  */
713  protected function getPersistentValues(PersistentObject $object) {
714  $values = [];
715 
716  // attribute definitions
717  $attributeDescs = $this->getAttributes();
718  foreach($attributeDescs as $curAttributeDesc) {
719  if ($curAttributeDesc instanceof RDBAttributeDescription) {
720  // add only attributes that are defined in the object
721  $attribName = $curAttributeDesc->getName();
722  //if ($object->hasValue($attribName)) {
723  $values[$attribName] = $object->getValue($attribName);
724  //}
725  }
726  }
727  return $values;
728  }
729 
730  /**
731  * Convert values before putting into storage
732  * @param $values Associative Array
733  * @return Associative Array
734  */
735  protected function convertValuesForStorage($values) {
736  // filter values according to type
737  foreach($values as $valueName => $value) {
738  $type = $this->getAttribute($valueName)->getType();
739  // integer
740  if (strpos(strtolower($type), 'int') === 0) {
741  $value = (strlen($value) == 0) ? null : intval($value);
742  $values[$valueName] = $value;
743  }
744  // null values
745  if ($value === null) {
746  $values[$valueName] = SQLConst::NULL();
747  }
748  }
749  return $values;
750  }
751 
752  /**
753  * Get the object which carries the sortkey in the relation of the given object
754  * and relative.
755  * @param PersistentObjectProxy $objectProxy
756  * @param PersistentObjectProxy $relativeProxy
757  * @param RelationDescription $relationDesc The relation description
758  * @return PersistentObjectProxy
759  */
760  protected function getSortableObject(PersistentObjectProxy $objectProxy,
761  PersistentObjectProxy $relativeProxy, RelationDescription $relationDesc) {
762  // in a many to many relation, we have to modify the order of the relation objects
763  if ($relationDesc instanceof RDBManyToManyRelationDescription) {
764  $nmObjects = $this->loadRelationObjects($objectProxy, $relativeProxy, $relationDesc);
765  return $nmObjects[0];
766  }
767  return $relativeProxy;
768  }
769 
770  /**
771  * Load the relation objects in a many to many relation from the database.
772  * @param $objectProxy The proxy at this end of the relation.
773  * @param $relativeProxy The proxy at the other end of the relation.
774  * @param $relationDesc The RDBManyToManyRelationDescription instance describing the relation.
775  * @param $includeTransaction Boolean whether to also search in the current transaction (default: false)
776  * @return Array of PersistentObject instances
777  */
778  protected function loadRelationObjects(PersistentObjectProxy $objectProxy,
779  PersistentObjectProxy $relativeProxy, RDBManyToManyRelationDescription $relationDesc,
780  $includeTransaction=false) {
781  $nmMapper = $this->persistenceFacade->getMapper($relationDesc->getThisEndRelation()->getOtherType());
782  $nmType = $nmMapper->getType();
783 
784  $thisId = $objectProxy->getOID()->getFirstId();
785  $otherId = $relativeProxy->getOID()->getFirstId();
786  $thisEndRelation = $relationDesc->getThisEndRelation();
787  $otherEndRelation = $relationDesc->getOtherEndRelation();
788  $thisFkAttr = $nmMapper->getAttribute($thisEndRelation->getFkName());
789  $otherFkAttr = $nmMapper->getAttribute($otherEndRelation->getFkName());
790 
791  $criteria1 = new Criteria($nmType, $thisFkAttr->getName(), "=", $thisId);
792  $criteria2 = new Criteria($nmType, $otherFkAttr->getName(), "=", $otherId);
793  $criteria = [$criteria1, $criteria2];
794  $nmObjects = $nmMapper->loadObjects($nmType, BuildDepth::SINGLE, $criteria);
795 
796  if ($includeTransaction) {
797  $transaction = $this->persistenceFacade->getTransaction();
798  $objects = $transaction->getObjects();
799  foreach ($objects as $object) {
800  if ($object->getType() == $nmType && $object instanceof Node) {
801  // we expect single valued relation ends
802  $thisEndObject = $object->getValue($thisEndRelation->getThisRole());
803  $otherEndObject = $object->getValue($otherEndRelation->getOtherRole());
804  if ($objectProxy->getOID() == $thisEndObject->getOID() &&
805  $relativeProxy->getOID() == $otherEndObject->getOID()) {
806  $nmObjects[] = $object;
807  }
808  }
809  }
810  }
811  return $nmObjects;
812  }
813 
814  /**
815  * @see RDBMapper::createPKCondition()
816  */
817  protected function createPKCondition(ObjectId $oid) {
818  $criterias = [];
819  $type = $this->getType();
820  $pkNames = $this->getPKNames();
821  $ids = $oid->getId();
822  for ($i=0, $count=sizeof($pkNames); $i<$count; $i++) {
823  $pkValue = $ids[$i];
824  $criterias[] = new Criteria($type, $pkNames[$i], "=", $pkValue);
825  }
826  return $criterias;
827  }
828 
829  /**
830  * Get all foreign key relations (used to reference a parent)
831  * @return An array of RDBManyToOneRelationDescription instances
832  */
833  protected function getForeignKeyRelations() {
834  if ($this->fkRelations == null) {
835  $this->fkRelations = [];
836  $relationDescs = $this->getRelations();
837  foreach($relationDescs as $relationDesc) {
838  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
839  $this->fkRelations[] = $relationDesc;
840  }
841  }
842  }
843  return $this->fkRelations;
844  }
845 
846  /**
847  * Check if a given attribute is a foreign key (used to reference a parent)
848  * @param $name The attribute name
849  * @return Boolean
850  * @note Public in order to be callable by ObjectQuery
851  */
852  public function isForeignKey($name) {
853  $fkDescs = $this->getForeignKeyRelations();
854  foreach($fkDescs as $fkDesc) {
855  if ($fkDesc->getFkName() == $name) {
856  return true;
857  }
858  }
859  return false;
860  }
861 
862  /**
863  * Get a unique string for the given parameter values
864  * @param $alias
865  * @param $attributeArray
866  * @param $criteriaArray
867  * @param $orderArray
868  * @param $pagingInfo
869  * @return String
870  */
871  protected function getCacheKey($alias, $attributeArray, $criteriaArray, $orderArray, PagingInfo $pagingInfo=null) {
872  $result = $this->getRealTableName().','.$alias.',';
873  if ($attributeArray != null) {
874  $result .= join(',', $attributeArray);
875  }
876  if ($criteriaArray != null) {
877  foreach ($criteriaArray as $c) {
878  $result .= $c->getId();
879  }
880  }
881  if ($orderArray != null) {
882  $result .= join(',', $orderArray);
883  }
884  if ($pagingInfo != null) {
885  $result .= ','.$pagingInfo->getOffset().','.$pagingInfo->getPageSize();
886  }
887  return $result;
888  }
889 }
890 ?>
getDefaultOrder($roleName=null)
Definition: RDBMapper.php:582
static fromObject($object)
Create a PersistenceProxy instance from a PersistentObject.
setState($state)
Set the state of the object to one of the STATE constants.
Instances of RDBAttributeDescription describe attributes of PersistentObjects in a relational databas...
getOID()
Get the object id of the PersistentObject.
UpdateOperation instances hold data necessary to accomplish an update operation on the persistent sto...
getOtherEndRelation()
Get the RDBManyToOneRelationDescription describing the relation between the connecting type and the '...
convertValuesForStorage($values)
Convert values before putting into storage.
getThisEndRelation()
Get the RDBOneToManyRelationDescription describing the relation between 'this' end and the connecting...
getManyToOneRelationSelectSQL(RelationDescription $relationDescription, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the statement for selecting a many-to-one relation.
addReferences(SelectStatement $selectStmt, $tableName)
Add the columns and joins to select references to a given select statement.
RDBMapper maps objects of one type to a relational database schema.
Definition: RDBMapper.php:49
static NULL()
Get the NULL expression.
Definition: SQLConst.php:28
IllegalArgumentException signals an exception in method arguments.
InsertOperation holds data necessary to accomplish an insert operation on the persistent store...
isForeignKey($name)
Check if a given attribute is a foreign key (used to reference a parent)
addOrderBy(SelectStatement $selectStmt, $orderby, $orderType, $aliasName, $defaultOrder)
Add the given order to the select statement.
getValue()
Get the value to compare the object with.
Definition: Criteria.php:124
getRealTableName()
Get the table name with the dbprefix added.
Definition: RDBMapper.php:284
join($name, $on, $columns=self::SQL_STAR, $type=self::JOIN_INNER)
getMapper()
Get the PersistenceMapper of the object.
getOID()
Get the object id of the PersistentObject.
getRelationStatement($thisAttr, $parameters, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the select statement for a many-to-one or one-to-many relation.
getNextId()
Get a new id for inserting into the database.
Definition: RDBMapper.php:204
static get(RDBMapper $mapper, $id=self::NO_CACHE)
Get the SelectStatement instance with the given id.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
getPersistentValues(PersistentObject $object)
Get an associative array of attribute name-value pairs to be stored for a given oject (references are...
addColumns(SelectStatement $selectStmt, $tableName, $attributes=null)
Add the columns to a given select statement.
Criteria defines a condition on a PersistentObject's attribute used to select specific instances...
Definition: Criteria.php:21
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
getRelationImpl($roleName, $includeManyToMany)
Internal implementation of PersistenceMapper::getRelation()
Definition: RDBMapper.php:450
getParameters($criteria, $tableName)
Get an array of placeholder/value pairs.
getForeignKeyRelations()
Get all foreign key relations (used to reference a parent)
static isDummyId($id)
Check if a given id is a dummy id.
Definition: ObjectId.php:234
Instances of RDBManyToOneRelationDescription describe a many to one relation from 'this' end (many) t...
PersistentObjectProxy is proxy for an PersistentObject instance.
getManyToManyRelationSelectSQL(RelationDescription $relationDescription, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the statement for selecting a many-to-many relation.
getType()
Get the entity type that this mapper handles.
getValue($name)
Get the value of an attribute.
DeleteOperation holds data necessary to accomplish an delete operation on the persistent store...
addCriteria(SelectStatement $selectStmt, $criteria, $tableName)
Add the given criteria to the select statement.
getCacheKey($alias, $attributeArray, $criteriaArray, $orderArray, PagingInfo $pagingInfo=null)
Get a unique string for the given parameter values.
renderCriteria(Criteria $criteria, $placeholder=null, $tableName=null, $columnName=null)
Render a Criteria instance as string.
Definition: RDBMapper.php:638
getSortableObject(PersistentObjectProxy $objectProxy, PersistentObjectProxy $relativeProxy, RelationDescription $relationDesc)
Get the object which carries the sortkey in the relation of the given object and relative.
Instances of RDBManyToManyRelationDescription describe a many to many relation from 'this' end to 'ot...
getRelations($hierarchyType='all')
Definition: RDBMapper.php:413
Instances of RelationDescription describe relations between different types of PersistentObjects.
NodeUnifiedRDBMapper maps Node objects to a relational database schema where each Node type has its o...
static isValid($oid)
Check if a serialized ObjectId has a valid syntax, the type is known and if the number of primary key...
Definition: ObjectId.php:122
setValue($name, $value, $forceSet=false, $trackChange=true)
Set the value of an attribute if it exists.
getOneToManyRelationSelectSQL(RelationDescription $relationDescription, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the statement for selecting a one-to-many relation.
loadRelationObjects(PersistentObjectProxy $objectProxy, PersistentObjectProxy $relativeProxy, RDBManyToManyRelationDescription $relationDesc, $includeTransaction=false)
Load the relation objects in a many to many relation from the database.
Instances of TransientAttributeDescription describe transient attributes of PersistentObjects.
Instances of ReferenceDescription describe reference attributes of PersistentObjects.
getReferences()
Get the references to other entities.
Definition: RDBMapper.php:540
Instances of RDBOneToManyRelationDescription describe a one to many relation from 'this' end (one) to...
getState()
Get the object's state:
getRelationSelectSQL(array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Node adds the concept of relations to PersistentObject.
Definition: Node.php:34
getAttributes(array $tags=[], $matchMode='all')
Definition: RDBMapper.php:507
getSelectSQL($criteria=null, $alias=null, $attributes=null, $orderby=null, PagingInfo $pagingInfo=null, $queryId=null)
getPkNames()
Get the names of the primary key values.
PersistentObject defines the interface of all persistent objects.