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  */
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) {
78  $object->setValue($sortkeyDef['sortFieldName'], $value);
79  }
80  }
81  $sortkeyDef = $this->getSortkey();
82  if ($sortkeyDef != 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  $selectStmt->offset($pagingInfo->getOffset());
264  }
265  }
266  else {
267  // on used statements only set parameters
268  $tableName = $alias != null ? $alias : $this->getRealTableName();
269  $parameters = $this->getParameters($criteria, $tableName);
270  }
271 
272  // set parameters
273  $selectStmt->setParameters($parameters);
274  return $selectStmt;
275  }
276 
277  /**
278  * @see RDBMapper::getRelationSelectSQL()
279  */
280  protected function getRelationSelectSQL(array $otherObjectProxies,
281  $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
282  $relationDescription = $this->getRelationIncludingNM($otherRole);
283  if ($relationDescription instanceof RDBManyToOneRelationDescription) {
284  return $this->getManyToOneRelationSelectSQL($relationDescription,
285  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
286  }
287  elseif ($relationDescription instanceof RDBOneToManyRelationDescription) {
288  return $this->getOneToManyRelationSelectSQL($relationDescription,
289  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
290  }
291  elseif ($relationDescription instanceof RDBManyToManyRelationDescription) {
292  return $this->getManyToManyRelationSelectSQL($relationDescription,
293  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
294  }
295  throw new IllegalArgumentException("Unknown RelationDescription for role: ".$otherRole);
296  }
297 
298  /**
299  * Get the statement for selecting a many-to-one relation
300  * @see RDBMapper::getRelationSelectSQL()
301  */
302  protected function getManyToOneRelationSelectSQL(RelationDescription $relationDescription,
303  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
304  PagingInfo $pagingInfo=null) {
305  $thisAttr = $this->getAttribute($relationDescription->getFkName());
306  $tableName = $this->getRealTableName();
307 
308  // id parameters
309  $parameters = [];
310  $idPlaceholder = ':'.$tableName.'_'.$thisAttr->getName();
311  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
312  $dbid = $otherObjectProxies[$i]->getValue($relationDescription->getIdName());
313  if ($dbid === null) {
314  $dbid = SQLConst::NULL();
315  }
316  $parameters[$idPlaceholder.$i] = $dbid;
317  }
318 
319  // statement
320  $selectStmt = $this->getRelationStatement($thisAttr, $parameters,
321  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
322  return [$selectStmt, $relationDescription->getIdName(), $relationDescription->getFkName()];
323  }
324 
325  /**
326  * Get the statement for selecting a one-to-many relation
327  * @see RDBMapper::getRelationSelectSQL()
328  */
329  protected function getOneToManyRelationSelectSQL(RelationDescription $relationDescription,
330  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
331  PagingInfo $pagingInfo=null) {
332  $thisAttr = $this->getAttribute($relationDescription->getIdName());
333  $tableName = $this->getRealTableName();
334 
335  // id parameters
336  $parameters = [];
337  $idPlaceholder = ':'.$tableName.'_'.$thisAttr->getName();
338  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
339  $fkValue = $otherObjectProxies[$i]->getValue($relationDescription->getFkName());
340  if ($fkValue === null) {
341  $fkValue = SQLConst::NULL();
342  }
343  $parameters[$idPlaceholder.$i] = $fkValue;
344  }
345 
346  // statement
347  $selectStmt = $this->getRelationStatement($thisAttr, $parameters,
348  $otherObjectProxies, $otherRole, $criteria, $orderby, $pagingInfo);
349  return [$selectStmt, $relationDescription->getFkName(), $relationDescription->getIdName()];
350 
351  }
352 
353  /**
354  * Get the select statement for a many-to-one or one-to-many relation.
355  * This method is the common part used in both relations.
356  * @see RDBMapper::getRelationSelectSQL()
357  */
358  protected function getRelationStatement($thisAttr, $parameters,
359  array $otherObjectProxies, $otherRole,
360  $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
361  $queryId = $this->getCacheKey($otherRole.sizeof($otherObjectProxies), null, $criteria, $orderby, $pagingInfo);
362  $tableName = $this->getRealTableName();
363  $selectStmt = SelectStatement::get($this, $queryId);
364  if (!$selectStmt->isCached()) {
365  // initialize the statement
366  $selectStmt->from($tableName, '');
367  $this->addColumns($selectStmt, $tableName);
368  $selectStmt->where($this->quoteIdentifier($tableName).'.'.
369  $this->quoteIdentifier($thisAttr->getName()).' IN('.join(',', array_keys($parameters)).')');
370  // order
371  $this->addOrderBy($selectStmt, $orderby, $this->getType(), $tableName, $this->getDefaultOrder($otherRole));
372  // additional conditions
373  $parameters = array_merge($parameters, $this->addCriteria($selectStmt, $criteria, $tableName));
374  // limit
375  if ($pagingInfo != null) {
376  $selectStmt->limit($pagingInfo->getPageSize());
377  $selectStmt->offset($pagingInfo->getOffset());
378  }
379  }
380  else {
381  // on used statements only set parameters
382  $parameters = array_merge($parameters, $this->getParameters($criteria, $tableName));
383  }
384 
385  // set parameters
386  $selectStmt->setParameters($parameters);
387  return $selectStmt;
388  }
389 
390  /**
391  * Get the statement for selecting a many-to-many relation
392  * @see RDBMapper::getRelationSelectSQL()
393  */
394  protected function getManyToManyRelationSelectSQL(RelationDescription $relationDescription,
395  array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null,
396  PagingInfo $pagingInfo=null) {
397  $thisRelationDesc = $relationDescription->getThisEndRelation();
398  $otherRelationDesc = $relationDescription->getOtherEndRelation();
399  $nmMapper = self::getMapper($thisRelationDesc->getOtherType());
400  $otherFkAttr = $nmMapper->getAttribute($otherRelationDesc->getFkName());
401  $nmTableName = $nmMapper->getRealTableName();
402 
403  // id parameters
404  $parameters = [];
405  $idPlaceholder = ':'.$nmTableName.'_'.$otherFkAttr->getName();
406  for ($i=0, $count=sizeof($otherObjectProxies); $i<$count; $i++) {
407  $dbid = $otherObjectProxies[$i]->getValue($thisRelationDesc->getIdName());
408  if ($dbid === null) {
409  $dbid = SQLConst::NULL();
410  }
411  $parameters[$idPlaceholder.$i] = $dbid;
412  }
413 
414  // statement
415  $queryId = $this->getCacheKey($otherRole.sizeof($otherObjectProxies), null, $criteria, $orderby, $pagingInfo);
416  $selectStmt = SelectStatement::get($this, $queryId);
417  if (!$selectStmt->isCached()) {
418  // initialize the statement
419 
420  $thisFkAttr = $nmMapper->getAttribute($thisRelationDesc->getFkName());
421  $thisIdAttr = $this->getAttribute($thisRelationDesc->getIdName());
422 
423  $tableName = $this->getRealTableName();
424  $selectStmt->from($tableName, '');
425  $this->addColumns($selectStmt, $tableName);
426  $joinCond = $nmTableName.'.'.$thisFkAttr->getName().'='.$tableName.'.'.$thisIdAttr->getName();
427  $joinColumns = [];
428  $selectStmt->where($this->quoteIdentifier($nmTableName).'.'.
429  $this->quoteIdentifier($otherFkAttr->getName()).' IN('.join(',', array_keys($parameters)).')');
430  // order (in this case we use the order of the many to many objects)
431  $nmSortDefs = $nmMapper->getDefaultOrder($otherRole);
432  $hasNmOrder = sizeof($nmSortDefs) > 0;
433  $orderType = $hasNmOrder ? $nmMapper->getType() : $this->getType();
434  $orderTable = $hasNmOrder ? $nmTableName : $tableName;
435  $defaultOrderDef = $hasNmOrder ? $nmSortDefs : $this->getDefaultOrder($otherRole);
436  $this->addOrderBy($selectStmt, $orderby, $orderType, $orderTable, $defaultOrderDef);
437  foreach($nmSortDefs as $nmSortDef) {
438  // add the sort attribute from the many to many object
439  $nmSortAttributeDesc = $nmMapper->getAttribute($nmSortDef['sortFieldName']);
440  $joinColumns[$nmSortAttributeDesc->getName()] = $nmSortAttributeDesc->getColumn();
441  }
442  // add proxy id
443  $joinColumns[self::INTERNAL_VALUE_PREFIX.'id'] = $otherFkAttr->getName();
444  $selectStmt->join($nmTableName, $joinCond, $joinColumns);
445  // additional conditions
446  $parameters = array_merge($parameters, $this->addCriteria($selectStmt, $criteria, $nmTableName));
447  // limit
448  if ($pagingInfo != null) {
449  $selectStmt->limit($pagingInfo->getPageSize(), $pagingInfo->getOffset());
450  }
451  }
452  else {
453  // on used statements only set parameters
454  $parameters = array_merge($parameters, $this->getParameters($criteria, $nmTableName));
455  }
456 
457  // set parameters
458  $selectStmt->setParameters($parameters);
459  return [$selectStmt, $thisRelationDesc->getIdName(), self::INTERNAL_VALUE_PREFIX.'id'];
460  }
461 
462  /**
463  * @see RDBMapper::getInsertSQL()
464  */
465  protected function getInsertSQL(PersistentObject $object) {
466  // get the attributes to store
467  $values = $this->convertValuesForStorage($this->getPersistentValues($object));
468 
469  // operations
470  $insertOp = new InsertOperation($this->getType(), $values);
471  $operations = [$insertOp];
472  return $operations;
473  }
474 
475  /**
476  * @see RDBMapper::getUpdateSQL()
477  */
478  protected function getUpdateSQL(PersistentObject $object) {
479  // get the attributes to store
480  $values = $this->convertValuesForStorage($this->getPersistentValues($object));
481 
482  // primary key definition
483  $pkCriteria = $this->createPKCondition($object->getOID());
484 
485  // operations
486  $updateOp = new UpdateOperation($this->getType(), $values, $pkCriteria);
487  $operations = [$updateOp];
488  return $operations;
489  }
490 
491  /**
492  * @see RDBMapper::getDeleteSQL()
493  */
494  protected function getDeleteSQL(ObjectId $oid) {
495  // primary key definition
496  $pkCriteria = $this->createPKCondition($oid);
497 
498  // operations
499  $deleteOp = new DeleteOperation($this->getType(), $pkCriteria);
500  $operations = [$deleteOp];
501  return $operations;
502  }
503 
504  /**
505  * Add the columns to a given select statement.
506  * @param $selectStmt The select statement (instance of SelectStatement)
507  * @param $tableName The table name
508  * @param $attributes Array of attribute names (optional)
509  * @return SelectStatement
510  */
511  protected function addColumns(SelectStatement $selectStmt, $tableName, $attributes=null) {
512  // columns
513  $attributeDescs = $this->getAttributes();
514  $columns = [];
515  foreach($attributeDescs as $curAttributeDesc) {
516  $name = $curAttributeDesc->getName();
517  if (($attributes == null || in_array($name, $attributes)) && $curAttributeDesc instanceof RDBAttributeDescription) {
518  $columns[$curAttributeDesc->getName()] = $curAttributeDesc->getColumn();
519  }
520  }
521  $selectStmt->columns($columns, true);
522 
523  // references
524  $selectStmt = $this->addReferences($selectStmt, $tableName);
525  return $selectStmt;
526  }
527 
528  /**
529  * Add the columns and joins to select references to a given select statement.
530  * @param $selectStmt The select statement (instance of SelectStatement)
531  * @param $tableName The name for this table (the alias, if used).
532  * @return SelectStatement
533  */
534  protected function addReferences(SelectStatement $selectStmt, $tableName) {
535  // collect all references first
536  $references = [];
537  foreach($this->getReferences() as $curReferenceDesc) {
538  $referencedType = $curReferenceDesc->getOtherType();
539  $referencedValue = $curReferenceDesc->getOtherName();
540  $relationDescs = $this->getRelationsByType($referencedType);
541  // get relation try role name if ambiguous
542  $relationDesc = sizeof($relationDescs) == 1 ? $relationDescs[0] : $this->getRelation($referencedType);
543  $otherMapper = self::getMapper($relationDesc->getOtherType());
544  if ($otherMapper) {
545  $otherTable = $otherMapper->getRealTableName();
546  $otherAttributeDesc = $otherMapper->getAttribute($referencedValue);
547  if ($otherAttributeDesc instanceof RDBAttributeDescription) {
548  // set up the join definition if not already defined
549  if (!isset($references[$otherTable])) {
550  $references[$otherTable] = [];
551  $references[$otherTable]['attributes'] = [];
552 
553  $tableNameQ = $tableName;
554  $otherTableQ = $otherTable.'Ref';
555 
556  // determine the join condition
557  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
558  // reference from parent
559  $thisAttrNameQ = $this->getAttribute($relationDesc->getFkName())->getColumn();
560  $otherAttrNameQ = $otherMapper->getAttribute($relationDesc->getIdName())->getColumn();
561  $additionalCond = "";
562  }
563  else if ($relationDesc instanceof RDBOneToManyRelationDescription) {
564  // reference from child
565  $thisAttrNameQ = $this->getAttribute($relationDesc->getIdName())->getColumn();
566  $otherAttrNameQ = $otherMapper->getAttribute($relationDesc->getFkName())->getColumn();
567  $otherPkNames = $otherMapper->getPkNames();
568  $otherPkNameQ = $otherMapper->getAttribute($otherPkNames[0])->getColumn();
569  $additionalCond = " AND ".$otherTableQ.".".$otherPkNameQ.
570  " = (SELECT MIN(".$otherTableQ.".".$otherPkNameQ.") FROM ".$otherTableQ.
571  " WHERE ".$otherTableQ.".".$otherAttrNameQ."=".$tableNameQ.".".$thisAttrNameQ.")";
572  }
573  $joinCond = $tableNameQ.".".$thisAttrNameQ."=".$otherTableQ.".".$otherAttrNameQ;
574  if (strlen($additionalCond) > 0) {
575  $joinCond = "(".$joinCond.$additionalCond.")";
576  }
577  $references[$otherTable]['joinCond'] = $joinCond;
578  }
579 
580  // add the attributes
581  $references[$otherTable]['attributes'][$curReferenceDesc->getName()] = $otherAttributeDesc->getColumn();
582  }
583  }
584  }
585  // add references from each referenced table
586  foreach($references as $otherTable => $curReference) {
587  $selectStmt->join([$otherTable.'Ref' => $otherTable], $curReference['joinCond'],
588  $curReference['attributes'], SelectStatement::JOIN_LEFT);
589  }
590  return $selectStmt;
591  }
592 
593  /**
594  * Add the given criteria to the select statement
595  * @param $selectStmt The select statement (instance of SelectStatement)
596  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (maybe null)
597  * @param $tableName The table name
598  * @return Array of placeholder/value pairs
599  */
600  protected function addCriteria(SelectStatement $selectStmt, $criteria, $tableName) {
601  $parameters = [];
602  if ($criteria != null) {
603  foreach ($criteria as $criterion) {
604  if ($criterion instanceof Criteria) {
605  $placeholder = ':'.$tableName.'_'.$criterion->getAttribute();
606  list($criteriaCondition, $criteriaPlaceholder) =
607  $this->renderCriteria($criterion, $placeholder, $tableName);
608  $selectStmt->where($criteriaCondition, $criterion->getCombineOperator());
609  if ($criteriaPlaceholder) {
610  $value = $criterion->getValue();
611  if (is_array($criteriaPlaceholder)) {
612  $parameters = array_merge($parameters, array_combine($criteriaPlaceholder, $value));
613  }
614  else {
615  $parameters[$criteriaPlaceholder] = $value;
616  }
617  }
618  }
619  else {
620  throw new IllegalArgumentException("The select condition must be an instance of Criteria");
621  }
622  }
623  }
624  return $parameters;
625  }
626 
627  /**
628  * Add the given order to the select statement
629  * @param $selectStmt The select statement (instance of SelectStatement)
630  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (maybe null)
631  * @param $orderType The type that define the attributes in orderby (maybe null)
632  * @param $aliasName The table alias name to be used (maybe null)
633  * @param $defaultOrder The default order definition to use, if orderby is null (@see PersistenceMapper::getDefaultOrder())
634  */
635  protected function addOrderBy(SelectStatement $selectStmt, $orderby, $orderType, $aliasName, $defaultOrder) {
636  if ($orderby == null) {
637  $orderby = [];
638  // use default ordering
639  if ($defaultOrder && sizeof($defaultOrder) > 0) {
640  foreach ($defaultOrder as $orderDef) {
641  $orderby[] = $orderDef['sortFieldName']." ".$orderDef['sortDirection'];
642  $orderType = $orderDef['sortType'];
643  }
644  }
645  }
646  for ($i=0, $count=sizeof($orderby); $i<$count; $i++) {
647  $curOrderBy = $orderby[$i];
648  $orderByParts = preg_split('/ /', $curOrderBy);
649  $orderAttribute = $orderByParts[0];
650  $orderDirection = sizeof($orderByParts) > 1 ? $orderByParts[1] : 'ASC';
651  if (strpos($orderAttribute, '.') > 0) {
652  // the type is included in the attribute
653  $orderAttributeParts = preg_split('/\./', $orderAttribute);
654  $orderAttribute = array_pop($orderAttributeParts);
655  }
656  $mapper = $orderType != null ? self::getMapper($orderType) : $this;
657  $orderAttributeDesc = $mapper->getAttribute($orderAttribute);
658  if ($orderAttributeDesc instanceof ReferenceDescription) {
659  // add the referenced column without table name
660  $mapper = self::getMapper($orderAttributeDesc->getOtherType());
661  $orderAttributeDesc = $mapper->getAttribute($orderAttributeDesc->getOtherName());
662  $orderColumnName = $orderAttributeDesc->getColumn();
663  }
664  elseif ($orderAttributeDesc instanceof TransientAttributeDescription) {
665  // skip, because no column exists
666  continue;
667  }
668  else {
669  // add the column with table name
670  $tableName = $aliasName != null ? $aliasName : $mapper->getRealTableName();
671  $orderColumnName = $tableName.'.'.$orderAttributeDesc->getColumn();
672  }
673  $selectStmt->order([$orderColumnName.' '.$orderDirection]);
674  }
675  }
676 
677  /**
678  * Get an array of placeholder/value pairs
679  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (maybe null)
680  * @param $tableName The table name
681  * @return Array of placeholder/value pairs
682  */
683  protected function getParameters($criteria, $tableName) {
684  $parameters = [];
685  if ($criteria != null) {
686  foreach ($criteria as $criterion) {
687  if ($criterion instanceof Criteria) {
688  $placeholder = ':'.$tableName.'_'.$criterion->getAttribute();
689  list($criteriaCondition, $criteriaPlaceholder) = $this->renderCriteria($criterion, $placeholder, '', '');
690  if ($criteriaPlaceholder) {
691  $value = $criterion->getValue();
692  if (is_array($criteriaPlaceholder)) {
693  $parameters = array_merge($parameters, array_combine($criteriaPlaceholder, $value));
694  }
695  else {
696  $parameters[$criteriaPlaceholder] = $value;
697  }
698  }
699  }
700  else {
701  throw new IllegalArgumentException("The select condition must be an instance of Criteria");
702  }
703  }
704  }
705  return $parameters;
706  }
707 
708  /**
709  * Get an associative array of attribute name-value pairs to be stored for a
710  * given oject (references are not included)
711  * @param $object The PeristentObject.
712  * @return Associative array
713  */
714  protected function getPersistentValues(PersistentObject $object) {
715  $values = [];
716 
717  // attribute definitions
718  $attributeDescs = $this->getAttributes();
719  foreach($attributeDescs as $curAttributeDesc) {
720  if ($curAttributeDesc instanceof RDBAttributeDescription) {
721  // add only attributes that are defined in the object
722  $attribName = $curAttributeDesc->getName();
723  //if ($object->hasValue($attribName)) {
724  $values[$attribName] = $object->getValue($attribName);
725  //}
726  }
727  }
728  return $values;
729  }
730 
731  /**
732  * Convert values before putting into storage
733  * @param $values Associative Array
734  * @return Associative Array
735  */
736  protected function convertValuesForStorage($values) {
737  // filter values according to type
738  foreach($values as $valueName => $value) {
739  $type = $this->getAttribute($valueName)->getType();
740  // integer
741  if (strpos(strtolower($type), 'int') === 0) {
742  $value = (strlen($value) == 0) ? null : intval($value);
743  $values[$valueName] = $value;
744  }
745  // null values
746  if ($value === null) {
747  $values[$valueName] = SQLConst::NULL();
748  }
749  }
750  return $values;
751  }
752 
753  /**
754  * Get the object which carries the sortkey in the relation of the given object
755  * and relative.
756  * @param PersistentObjectProxy $objectProxy
757  * @param PersistentObjectProxy $relativeProxy
758  * @param RelationDescription $relationDesc The relation description
759  * @return PersistentObjectProxy
760  */
761  protected function getSortableObject(PersistentObjectProxy $objectProxy,
762  PersistentObjectProxy $relativeProxy, RelationDescription $relationDesc) {
763  // in a many to many relation, we have to modify the order of the relation objects
764  if ($relationDesc instanceof RDBManyToManyRelationDescription) {
765  $nmObjects = $this->loadRelationObjects($objectProxy, $relativeProxy, $relationDesc);
766  return $nmObjects[0];
767  }
768  return $relativeProxy;
769  }
770 
771  /**
772  * Load the relation objects in a many to many relation from the database.
773  * @param $objectProxy The proxy at this end of the relation.
774  * @param $relativeProxy The proxy at the other end of the relation.
775  * @param $relationDesc The RDBManyToManyRelationDescription instance describing the relation.
776  * @param $includeTransaction Boolean whether to also search in the current transaction (default: false)
777  * @return Array of PersistentObject instances
778  */
779  protected function loadRelationObjects(PersistentObjectProxy $objectProxy,
780  PersistentObjectProxy $relativeProxy, RDBManyToManyRelationDescription $relationDesc,
781  $includeTransaction=false) {
782  $nmMapper = self::getMapper($relationDesc->getThisEndRelation()->getOtherType());
783  $nmType = $nmMapper->getType();
784 
785  $thisId = $objectProxy->getOID()->getFirstId();
786  $otherId = $relativeProxy->getOID()->getFirstId();
787  $thisEndRelation = $relationDesc->getThisEndRelation();
788  $otherEndRelation = $relationDesc->getOtherEndRelation();
789  $thisFkAttr = $nmMapper->getAttribute($thisEndRelation->getFkName());
790  $otherFkAttr = $nmMapper->getAttribute($otherEndRelation->getFkName());
791 
792  $criteria1 = new Criteria($nmType, $thisFkAttr->getName(), "=", $thisId);
793  $criteria2 = new Criteria($nmType, $otherFkAttr->getName(), "=", $otherId);
794  $criteria = [$criteria1, $criteria2];
795  $nmObjects = $nmMapper->loadObjects($nmType, BuildDepth::SINGLE, $criteria);
796 
797  if ($includeTransaction) {
798  $transaction = $this->persistenceFacade->getTransaction();
799  $objects = $transaction->getObjects();
800  foreach ($objects as $object) {
801  if ($object->getType() == $nmType && $object instanceof Node) {
802  // we expect single valued relation ends
803  $thisEndObject = $object->getValue($thisEndRelation->getThisRole());
804  $otherEndObject = $object->getValue($otherEndRelation->getOtherRole());
805  if ($objectProxy->getOID() == $thisEndObject->getOID() &&
806  $relativeProxy->getOID() == $otherEndObject->getOID()) {
807  $nmObjects[] = $object;
808  }
809  }
810  }
811  }
812  return $nmObjects;
813  }
814 
815  /**
816  * @see RDBMapper::createPKCondition()
817  */
818  protected function createPKCondition(ObjectId $oid) {
819  $criterias = [];
820  $type = $this->getType();
821  $pkNames = $this->getPKNames();
822  $ids = $oid->getId();
823  for ($i=0, $count=sizeof($pkNames); $i<$count; $i++) {
824  $pkValue = $ids[$i];
825  $criterias[] = new Criteria($type, $pkNames[$i], "=", $pkValue);
826  }
827  return $criterias;
828  }
829 
830  /**
831  * Get all foreign key relations (used to reference a parent)
832  * @return An array of RDBManyToOneRelationDescription instances
833  */
834  protected function getForeignKeyRelations() {
835  if ($this->fkRelations == null) {
836  $this->fkRelations = [];
837  $relationDescs = $this->getRelations();
838  foreach($relationDescs as $relationDesc) {
839  if ($relationDesc instanceof RDBManyToOneRelationDescription) {
840  $this->fkRelations[] = $relationDesc;
841  }
842  }
843  }
844  return $this->fkRelations;
845  }
846 
847  /**
848  * Check if a given attribute is a foreign key (used to reference a parent)
849  * @param $name The attribute name
850  * @return Boolean
851  * @note Public in order to be callable by ObjectQuery
852  */
853  public function isForeignKey($name) {
854  $fkDescs = $this->getForeignKeyRelations();
855  foreach($fkDescs as $fkDesc) {
856  if ($fkDesc->getFkName() == $name) {
857  return true;
858  }
859  }
860  return false;
861  }
862 
863  /**
864  * Get a unique string for the given parameter values
865  * @param $alias
866  * @param $attributeArray
867  * @param $criteriaArray
868  * @param $orderArray
869  * @param $pagingInfo
870  * @return String
871  */
872  protected function getCacheKey($alias, $attributeArray, $criteriaArray, $orderArray, PagingInfo $pagingInfo=null) {
873  $result = $this->getRealTableName().','.$alias.',';
874  if ($attributeArray != null) {
875  $result .= join(',', $attributeArray);
876  }
877  if ($criteriaArray != null) {
878  foreach ($criteriaArray as $c) {
879  $result .= $c->getId();
880  }
881  }
882  if ($orderArray != null) {
883  $result .= join(',', $orderArray);
884  }
885  if ($pagingInfo != null) {
886  $result .= ','.$pagingInfo->getOffset().','.$pagingInfo->getPageSize();
887  }
888  return $result;
889  }
890 }
891 ?>
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.
getCacheKey($alias, $attributeArray, $criteriaArray, $orderArray, PagingInfo $pagingInfo=null)
Get a unique string for the given parameter values.
static fromObject($object)
Create a PersistenceProxy instance from a PersistentObject.
addCriteria(SelectStatement $selectStmt, $criteria, $tableName)
Add the given criteria to the select statement.
getSortableObject(PersistentObjectProxy $objectProxy, PersistentObjectProxy $relativeProxy, RelationDescription $relationDesc)
Get the object which carries the sortkey in the relation of the given object and relative.
setState($state)
Set the state of the object to one of the STATE constants.
getParameters($criteria, $tableName)
Get an array of placeholder/value pairs.
Instances of RDBAttributeDescription describe attributes of PersistentObjects in a relational databas...
getForeignKeyRelations()
Get all foreign key relations (used to reference a parent)
getOID()
Get the object id of the PersistentObject.
renderCriteria(Criteria $criteria, $placeholder=null, $tableName=null, $columnName=null)
getNextId()
Get a new id for inserting into the database.
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 '...
loadRelationObjects(PersistentObjectProxy $objectProxy, PersistentObjectProxy $relativeProxy, RDBManyToManyRelationDescription $relationDesc, $includeTransaction=false)
Load the relation objects in a many to many relation from the database.
getThisEndRelation()
Get the RDBOneToManyRelationDescription describing the relation between 'this' end and the connecting...
getSelectSQL($criteria=null, $alias=null, $attributes=null, $orderby=null, PagingInfo $pagingInfo=null, $queryId=null)
getPersistentValues(PersistentObject $object)
Get an associative array of attribute name-value pairs to be stored for a given oject (references are...
static NULL()
Get the NULL expression.
Definition: SQLConst.php:28
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.
getOneToManyRelationSelectSQL(RelationDescription $relationDescription, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the statement for selecting a one-to-many relation.
InsertOperation holds data necessary to accomplish an insert operation on the persistent store...
getValue()
Get the value to compare the object with.
Definition: Criteria.php:124
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.
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
getManyToOneRelationSelectSQL(RelationDescription $relationDescription, array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the statement for selecting a many-to-one relation.
NodeUnifiedRDBMapper maps Node objects to a relational database schema where each Node type has its o...
getAttributes(array $tags=[], $matchMode='all')
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
addReferences(SelectStatement $selectStmt, $tableName)
Add the columns and joins to select references to a given select statement.
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.
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...
addColumns(SelectStatement $selectStmt, $tableName, $attributes=null)
Add the columns to a given select statement.
getRelationSelectSQL(array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
addOrderBy(SelectStatement $selectStmt, $orderby, $orderType, $aliasName, $defaultOrder)
Add the given order to the select statement.
Instances of RDBManyToManyRelationDescription describe a many to many relation from 'this' end to 'ot...
isForeignKey($name)
Check if a given attribute is a foreign key (used to reference a parent)
Instances of RelationDescription describe relations between different types of PersistentObjects.
getRelationIncludingNM($roleName)
Implementation of PersistenceMapper::getRelation() including nm classes in many to many relations...
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.
AbstractRDBMapper maps objects of one type to a relational database schema.
Instances of TransientAttributeDescription describe transient attributes of PersistentObjects.
Instances of ReferenceDescription describe reference attributes of PersistentObjects.
wCMF - wemove Content Management Framework Copyright (C) 2005-2017 wemove digital solutions GmbH ...
convertValuesForStorage($values)
Convert values before putting into storage.
Instances of RDBOneToManyRelationDescription describe a one to many relation from 'this' end (one) to...
getState()
Get the object's state:
Node adds the concept of relations to PersistentObject.
Definition: Node.php:34
getPkNames()
Get the names of the primary key values.
PersistentObject defines the interface of all persistent objects.