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