AbstractRDBMapper.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 
13 use PDO;
39 use Laminas\EventManager\EventManager as LaminasEventManager;
40 use Laminas\Db\Adapter\Adapter;
41 use Laminas\Db\TableGateway\Feature\EventFeature;
42 use Laminas\Db\TableGateway\Feature\EventFeatureEventsInterface;
43 use Laminas\Db\TableGateway\TableGateway;
44 
45 /**
46  * AbstractRDBMapper maps objects of one type to a relational database schema.
47  * It defines a persistence mechanism that specialized mappers customize by overriding
48  * the given template methods.
49  *
50  * @author ingo herwig <ingo@wemove.com>
51  */
52 abstract class AbstractRDBMapper extends AbstractMapper implements RDBMapper {
53 
54  const SEQUENCE_CLASS = 'DBSequence';
55 
56  private static $adapters = []; // registry for adapters, key: connId
57  private static $inTransaction = []; // registry for transaction status (boolean), key: connId
58  private static $sequenceMapper = null;
59  private static $isDebugEnabled = false;
60  private static $logger = null;
61  private static $dbEvents = null;
62 
63  private $connectionParams = null; // database connection parameters
64  private $connId = null; // a connection identifier composed of the connection parameters
65  private $adapter = null; // database adapter
66  private $dbPrefix = ''; // database prefix (if given in the configuration file)
67  private $isFileDB = false;
68 
69  private $relations = null;
70  private $attributes = null;
71 
72  // prepared statements
73  private $idSelectStmt = null;
74  private $idInsertStmt = null;
75  private $idUpdateStmt = null;
76 
77  // statement collected inside a transaction
78  private $statements = [];
79 
80  // keeps track of currently loading relations to avoid circular loading
81  private $loadingRelations = [];
82 
83  const INTERNAL_VALUE_PREFIX = '_mapper_internal_';
84 
85  /**
86  * Constructor
87  * @param $persistenceFacade
88  * @param $permissionManager
89  * @param $concurrencyManager
90  * @param $eventManager
91  */
96  parent::__construct($persistenceFacade, $permissionManager,
98  if (self::$logger == null) {
99  self::$logger = LogManager::getLogger(__CLASS__);
100  }
101  self::$isDebugEnabled = self::$logger->isDebugEnabled();
102 
103  // listen to db events
104  self::$dbEvents = new LaminasEventManager();
105  self::$dbEvents->attach(EventFeatureEventsInterface::EVENT_POST_INSERT, [$this, 'handleDbEvent']);
106  self::$dbEvents->attach(EventFeatureEventsInterface::EVENT_POST_UPDATE, [$this, 'handleDbEvent']);
107  self::$dbEvents->attach(EventFeatureEventsInterface::EVENT_POST_DELETE, [$this, 'handleDbEvent']);
108  }
109 
110  /**
111  * Destructor
112  */
113  public function __destruct() {
114  self::$dbEvents->detach([$this, 'handleDbEvent']);
115  }
116 
117  /**
118  * Select data to be stored in the session.
119  * PDO throws an exception if tried to be (un-)serialized.
120  */
121  public function __sleep() {
122  return ['connectionParams', 'dbPrefix'];
123  }
124 
125  /**
126  * Set the connection parameters.
127  * @param $params Initialization data given in an associative array with the following keys:
128  * dbType, dbHostName, dbUserName, dbPassword, dbName
129  * if dbPrefix is given it will be appended to every table string, which is
130  * useful if different applications operate on the same database
131  */
132  public function setConnectionParams($params) {
133  $this->connectionParams = $params;
134  if (isset($this->connectionParams['dbPrefix'])) {
135  $this->dbPrefix = $this->connectionParams['dbPrefix'];
136  }
137  }
138 
139  /**
140  * Get the connection parameters.
141  * @return Assoziative array with the following keys:
142  * dbType, dbHostName, dbUserName, dbPassword, dbName, dbPrefix
143  */
144  public function getConnectionParams() {
145  return $this->connectionParams;
146  }
147 
148  /**
149  * @see RDBMapper::getConnection()
150  */
151  public function getConnection() {
152  if ($this->adapter == null) {
153  $this->connect();
154  }
155  return $this->adapter->getDriver()->getConnection()->getResource();
156  }
157 
158  /**
159  * @see RDBMapper::getAdapter()
160  */
161  public function getAdapter() {
162  if ($this->adapter == null) {
163  $this->connect();
164  }
165  return $this->adapter;
166  }
167 
168  /**
169  * Get the mapper for a Node and optionally check if it is a supported one.
170  * @param $type The type of Node to get the mapper for
171  * @param $strict Boolean indicating if the mapper must be an instance of RDBMapper (default true)
172  * @return RDBMapper instance
173  */
174  protected static function getMapper($type, $strict=true) {
175  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
176  $mapper = $persistenceFacade->getMapper($type);
177  if ($strict && !($mapper instanceof RDBMapper)) {
178  throw new PersistenceException('Only PersistenceMappers of type RDBMapper are supported.');
179  }
180  return $mapper;
181  }
182 
183  /**
184  * Actually connect to the database using the configuration parameters given
185  * to the constructor. The implementation ensures that only one connection is
186  * used for all RDBMappers with the same configuration parameters.
187  */
188  private function connect() {
189  // connect
190  if (isset($this->connectionParams['dbType']) && isset($this->connectionParams['dbHostName']) &&
191  isset($this->connectionParams['dbUserName']) && isset($this->connectionParams['dbPassword']) &&
192  isset($this->connectionParams['dbName'])) {
193 
194  $dbType = strtolower($this->connectionParams['dbType']);
195  $this->isFileDB = $dbType == 'sqlite' && strtolower($this->connectionParams['dbName']) != ':memory:';
196 
197  $this->connId = join(',', [$this->connectionParams['dbType'], $this->connectionParams['dbHostName'],
198  $this->connectionParams['dbUserName'], $this->connectionParams['dbPassword'], $this->connectionParams['dbName'],
199  // make sure that the sequence mapper uses it's own connection for separate transaction management
200  (!$this->isFileDB && $this->getType() == $this->getSequenceMapper()->getType())
201  ]);
202 
203  // reuse an existing adapter if possible
204  if (isset(self::$adapters[$this->connId])) {
205  $this->adapter = self::$adapters[$this->connId];
206  }
207  else {
208  try {
209  $charSet = isset($this->connectionParams['dbCharSet']) ?
210  $this->connectionParams['dbCharSet'] : 'utf8';
211 
212  // create new connection
213  $pdoParams = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
214  // driver specific
215  switch ($dbType) {
216  case 'mysql':
217  $pdoParams[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true;
218  $pdoParams[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES ".$charSet;
219  break;
220  case 'sqlite':
221  if (!$this->isFileDB) {
222  $pdoParams[PDO::ATTR_PERSISTENT] = true;
223  }
224  else {
225  $this->connectionParams['dbName'] = FileUtil::realpath(WCMF_BASE.$this->connectionParams['dbName']);
226  }
227  break;
228  }
229 
230  $params = [
231  'hostname' => $this->connectionParams['dbHostName'],
232  'username' => $this->connectionParams['dbUserName'],
233  'password' => $this->connectionParams['dbPassword'],
234  'database' => $this->connectionParams['dbName'],
235  'driver' => 'Pdo_'.ucfirst($this->connectionParams['dbType']),
236  'driver_options' => $pdoParams
237  ];
238 
239  if (!empty($this->connectionParams['dbPort'])) {
240  $params['port'] = $this->connectionParams['dbPort'];
241  }
242  $this->adapter = new Adapter($params);
243 
244  // store the connection for reuse
245  self::$adapters[$this->connId] = $this->adapter;
246  }
247  catch(\Exception $ex) {
248  throw new PersistenceException("Connection to ".$this->connectionParams['dbHostName'].".".
249  $this->connectionParams['dbName']." failed: ".$ex->getMessage());
250  }
251  }
252  // get database prefix if defined
253  if (isset($this->connectionParams['dbPrefix'])) {
254  $this->dbPrefix = $this->connectionParams['dbPrefix'];
255  }
256  }
257  else {
258  throw new IllegalArgumentException("Wrong parameters for constructor.");
259  }
260  }
261 
262  /**
263  * Get the sequence mapper
264  * @return PersistenceMapper
265  */
266  protected function getSequenceMapper() {
267  if (self::$sequenceMapper == null) {
268  self::$sequenceMapper = self::getMapper(self::SEQUENCE_CLASS);
269  }
270  return self::$sequenceMapper;
271  }
272 
273  /**
274  * Get a new id for inserting into the database
275  * @return An id value.
276  */
277  protected function getNextId() {
278  try {
279  $sequenceMapper = $this->getSequenceMapper();
280  $sequenceTable = $sequenceMapper->getRealTableName();
281  $sequenceConn = $sequenceMapper->getConnection();
282  $tableName = strtolower($this->getRealTableName());
283 
284  // run id sequence in it's own transaction, if supported
285  if (!$this->isFileDB) {
286  $sequenceConn->beginTransaction();
287  }
288  if ($this->idSelectStmt == null) {
289  $this->idSelectStmt = $sequenceConn->prepare("SELECT ".$this->quoteIdentifier("id").
290  " FROM ".$this->quoteIdentifier($sequenceTable)." WHERE ".
291  $this->quoteIdentifier("table")."=".$this->quoteValue($tableName));
292  }
293  if ($this->idInsertStmt == null) {
294  $this->idInsertStmt = $sequenceConn->prepare("INSERT INTO ".
295  $this->quoteIdentifier($sequenceTable)." (".$this->quoteIdentifier("id").
296  ", ".$this->quoteIdentifier("table").") VALUES (1, ".
297  $this->quoteValue($tableName).")");
298  }
299  if ($this->idUpdateStmt == null) {
300  $this->idUpdateStmt = $sequenceConn->prepare("UPDATE ".$this->quoteIdentifier($sequenceTable).
301  " SET ".$this->quoteIdentifier("id")."=(".$this->quoteIdentifier("id")."+1) WHERE ".
302  $this->quoteIdentifier("table")."=".$this->quoteValue($tableName));
303  }
304  $this->idSelectStmt->execute();
305  $rows = $this->idSelectStmt->fetchAll(PDO::FETCH_ASSOC);
306  if (sizeof($rows) == 0) {
307  $this->idInsertStmt->execute();
308  $this->idInsertStmt->closeCursor();
309  $rows = [['id' => 1]];
310  }
311  $id = $rows[0]['id'];
312  $this->idUpdateStmt->execute();
313  $this->idUpdateStmt->closeCursor();
314  $this->idSelectStmt->closeCursor();
315  if (!$this->isFileDB) {
316  $sequenceConn->commit();
317  }
318  return $id;
319  }
320  catch (\Exception $ex) {
321  self::$logger->error("The next id query caused the following exception:\n".$ex->getMessage());
322  throw new PersistenceException("Error in persistent operation. See log file for details.");
323  }
324  }
325 
326  /**
327  * @see RDBMapper::getQuoteIdentifierSymbol()
328  */
329  public function getQuoteIdentifierSymbol() {
330  if ($this->adapter == null) {
331  $this->connect();
332  }
333  return $this->adapter->getPlatform()->getQuoteIdentifierSymbol();
334  }
335 
336  /**
337  * @see RDBMapper::quoteIdentifier()
338  */
339  public function quoteIdentifier($identifier) {
340  if ($this->adapter == null) {
341  $this->connect();
342  }
343  return $this->adapter->getPlatform()->quoteIdentifier($identifier);
344  }
345 
346  /**
347  * @see RDBMapper::quoteValue()
348  */
349  public function quoteValue($value) {
350  if ($this->adapter == null) {
351  $this->connect();
352  }
353  return $this->adapter->getPlatform()->quoteValue($value);
354  }
355 
356  /**
357  * @see RDBMapper::getRealTableName()
358  */
359  public function getRealTableName() {
360  return $this->dbPrefix.$this->getTableName();
361  }
362 
363  /**
364  * Execute a query on the connection.
365  * @param $sql The SQL statement as string
366  * @param $parameters An associative array of parameter name/values pairs to replace the placeholders with (optional, default: empty array)
367  * @return If the query is a select, an array of associative arrays containing the selected data,
368  * the number of affected rows else
369  */
370  public function executeSql($sql, $parameters=[]) {
371  if ($this->adapter == null) {
372  $this->connect();
373  }
374  try {
375  $stmt = $this->adapter->createStatement($sql, $parameters);
376  $results = $stmt->execute();
377  if ($results->isQueryResult()) {
378  return $results->getResource()->fetchAll();
379  }
380  else {
381  return $results->getAffectedRows();
382  }
383  }
384  catch (\Exception $ex) {
385  self::$logger->error("The query: ".$sql."\ncaused the following exception:\n".$ex->getMessage());
386  throw new PersistenceException("Error in persistent operation. See log file for details.");
387  }
388  }
389 
390  /**
391  * @see RDBMapper::select()
392  */
393  public function select(SelectStatement $selectStmt, PagingInfo $pagingInfo=null) {
394  if ($this->adapter == null) {
395  $this->connect();
396  }
397  try {
398  if ($pagingInfo != null) {
399  // make a count query if requested
400  if (!$pagingInfo->isIgnoringTotalCount()) {
401  $pagingInfo->setTotalCount($selectStmt->getRowCount());
402  }
403  // return empty array, if page size <= 0
404  if ($pagingInfo->getPageSize() <= 0) {
405  return [];
406  }
407  }
408  if (self::$isDebugEnabled) {
409  self::$logger->debug("Execute statement: ".$selectStmt->__toString());
410  self::$logger->debug($selectStmt->getParameters());
411  }
412  $result = $selectStmt->query();
413  // save statement on success
414  $selectStmt->save();
415  $rows = $result->fetchAll();
416  if (self::$isDebugEnabled) {
417  self::$logger->debug("Result: ".sizeof($rows)." row(s)");
418  }
419  return $rows;
420  }
421  catch (\Exception $ex) {
422  self::$logger->error("The query: ".$selectStmt->__toString()."\ncaused the following exception:\n".$ex->getMessage());
423  throw new PersistenceException("Error in persistent operation. See log file for details.");
424  }
425  }
426 
427  /**
428  * @see PersistenceMapper::executeOperation()
429  */
430  public function executeOperation(PersistenceOperation $operation) {
431  if ($operation->getType() != $this->getType()) {
432  throw new IllegalArgumentException("Operation: ".$operation.
433  " can't be executed by ".get_class($this));
434  }
435  if ($this->adapter == null) {
436  $this->connect();
437  }
438 
439  // transform table name
440  $tableName = $this->getRealTableName();
441 
442  // translate value names to columns
443  $translatedValues = [];
444  foreach($operation->getValues() as $name => $value) {
445  $attrDesc = $this->getAttribute($name);
446  if ($attrDesc) {
447  $translatedValues[$attrDesc->getColumn()] = $value;
448  }
449  }
450 
451  // transform criteria
452  $where = [];
453  foreach ($operation->getCriteria() as $criterion) {
454  list($criteriaCondition) = $this->renderCriteria($criterion, null, $tableName);
455  $where[] = $criteriaCondition;
456  }
457 
458  // execute the statement
459  $affectedRows = 0;
460  $table = new TableGateway($tableName, $this->adapter, new EventFeature(self::$dbEvents));
461  try {
462  if ($operation instanceof InsertOperation) {
463  $affectedRows = $table->insert($translatedValues);
464  }
465  elseif ($operation instanceof UpdateOperation) {
466  $affectedRows = $table->update($translatedValues, $where);
467  }
468  elseif ($operation instanceof DeleteOperation) {
469  $affectedRows = $table->delete($where);
470  }
471  else {
472  throw new IllegalArgumentException("Unsupported Operation: ".$operation);
473  }
474  }
475  catch (\Exception $ex) {
476  self::$logger->error("The operation: ".$operation."\ncaused the following exception:\n".$ex->getMessage());
477  throw new PersistenceException("Error in persistent operation. See log file for details.");
478  }
479  return $affectedRows;
480  }
481 
482  /**
483  * @see PersistenceMapper::getRelations()
484  */
485  public function getRelations($hierarchyType='all') {
486  $this->initRelations();
487  return $hierarchyType == 'all' ? $this->relations['all'] : $this->relations[$hierarchyType];
488  }
489 
490  /**
491  * Implementation of PersistenceMapper::getRelation() including nm classes in
492  * many to many relations
493  * @param $roleName The role name of the relation
494  * @return RelationDescription
495  */
496  protected function getRelationIncludingNM($roleName) {
497  $this->initRelations();
498  if ($this->hasRelation($roleName)) {
499  return $this->getRelation($roleName);
500  }
501  elseif (isset($this->relations['nm'][$roleName])) {
502  return $this->relations['nm'][$roleName];
503  }
504  else {
505  throw new PersistenceException("No relation to '".$roleName."' exists in '".$this->getType()."'");
506  }
507  }
508 
509  /**
510  * Get the relation descriptions defined in the subclass and add them to internal arrays.
511  */
512  private function initRelations() {
513  if ($this->relations == null) {
514  $this->relations = [];
515  $this->relations['all'] = array_values($this->getRelationDescriptions());
516  $this->relations['parent'] = [];
517  $this->relations['child'] = [];
518  $this->relations['undefined'] = [];
519  $this->relations['nm'] = [];
520 
521  foreach ($this->relations['all'] as $relation) {
522  $hierarchyType = $relation->getHierarchyType();
523  $hierarchyKey = $hierarchyType == 'parent' || $hierarchyType == 'child' ?
524  $hierarchyType : 'undefined';
525  $this->relations[$hierarchyKey][] = $relation;
526 
527  // also store relations to many to many objects, because
528  // they would be invisible otherwise
529  if ($relation instanceof RDBManyToManyRelationDescription) {
530  $nmRelation = $relation->getThisEndRelation();
531  $this->relations['nm'][$nmRelation->getOtherRole()] = $nmRelation;
532  }
533  }
534  }
535  }
536 
537  /**
538  * @see PersistenceMapper::getAttributes()
539  */
540  public function getAttributes(array $tags=[], $matchMode='all') {
541  $this->initAttributes();
542  $result = [];
543  if (sizeof($tags) == 0) {
544  $result = $this->attributes;
545  }
546  else {
547  foreach ($this->attributes as $attribute) {
548  if ($attribute->matchTags($tags, $matchMode)) {
549  $result[] = $attribute;
550  }
551  }
552  }
553  return $result;
554  }
555 
556  /**
557  * Get the attribute descriptions defined in the subclass and add them to internal arrays.
558  */
559  private function initAttributes() {
560  if ($this->attributes == null) {
561  $this->attributes = array_values($this->getAttributeDescriptions());
562  }
563  }
564 
565  /**
566  * @see PersistenceMapper::isSortable()
567  */
568  public function isSortable($roleName=null) {
569  return $this->getSortkey($roleName) != null;
570  }
571 
572  /**
573  * @see PersistenceMapper::getSortkey()
574  */
575  public function getSortkey($roleName=null) {
576  $sortDefs = $this->getDefaultOrder($roleName);
577  if (sizeof($sortDefs) > 0 && $sortDefs[0]['isSortkey'] == true) {
578  return $sortDefs[0];
579  }
580  return null;
581  }
582 
583  /**
584  * @see PersistenceMapper::getDefaultOrder()
585  */
586  public function getDefaultOrder($roleName=null) {
587  $sortDef = null;
588  $sortType = null;
589  if ($roleName != null && $this->hasRelation($roleName) &&
590  ($relationDesc = $this->getRelation($roleName)) instanceof RDBManyToManyRelationDescription) {
591 
592  // the order may be overriden by the many to many relation class
593  $thisRelationDesc = $relationDesc->getThisEndRelation();
594  $nmMapper = $thisRelationDesc->getOtherMapper($thisRelationDesc->getOtherType());
595  $sortDef = $nmMapper->getOwnDefaultOrder($roleName);
596  $sortType = $nmMapper->getType();
597  }
598  if (!$sortDef || sizeof($sortDef) == 0) {
599  // default: the order is defined in this mapper
600  $sortDef = $this->getOwnDefaultOrder($roleName);
601  $sortType = $this->getType();
602  }
603  // add the sortType parameter to the result
604  for ($i=0, $count=sizeof($sortDef); $i<$count; $i++) {
605  $sortDef[$i]['sortType'] = $sortType;
606  }
607  return $sortDef;
608  }
609 
610  /**
611  * Check if a value is a primary key value
612  * @param $name The name of the value
613  * @return Boolean
614  */
615  protected function isPkValue($name) {
616  $pkNames = $this->getPKNames();
617  return in_array($name, $pkNames);
618  }
619 
620  /**
621  * @see RDBMapper::constructOID()
622  */
623  public function constructOID($data) {
624  $pkNames = $this->getPkNames();
625  $ids = [];
626  foreach ($pkNames as $pkName) {
627  $ids[] = $data[$pkName];
628  }
629  return new ObjectId($this->getType(), $ids);
630  }
631 
632  /**
633  * @see RDBMapper::renderCriteria()
634  */
635  public function renderCriteria(Criteria $criteria, $placeholder=null, $tableName=null, $columnName=null) {
636  $type = $criteria->getType();
637  if (!$this->persistenceFacade->isKnownType($type)) {
638  throw new IllegalArgumentException("Unknown type referenced in Criteria: $type");
639  }
640 
641  // map type and attribute, if necessary
642  $mapper = self::getMapper($type);
643  if ($tableName === null) {
644  $tableName = $mapper->getRealTableName();
645  }
646  if ($columnName === null) {
647  $attrDesc = $mapper->getAttribute($criteria->getAttribute());
648  $columnName = $attrDesc->getColumn();
649  }
650 
651  $condition = $mapper->quoteIdentifier($tableName).".".$mapper->quoteIdentifier($columnName);
652  $operator = $criteria->getOperator();
653  $operatorLC = strtolower($criteria->getOperator());
654  $value = $criteria->getValue();
655  if (($operator == '=' || $operator == '!=') && $value === null) {
656  // handle null values
657  $condition .= " IS ".($operator == '!=' ? "NOT " : "")."NULL";
658  $placeholder = null;
659  }
660  elseif ($operatorLC == 'in' || $operatorLC == 'not in') {
661  $array = !$placeholder ? array_map([$mapper, 'quoteValue'], $value) :
662  array_map(function($i) use ($placeholder) {
663  return $placeholder.$i;
664  }, range(0, sizeof($value)-1));
665  $condition .= " ".strtoupper($operator)." (".join(', ', $array).")";
666  $placeholder = !$placeholder ? null : $array;
667  }
668  else {
669  $condition .= " ".$criteria->getOperator()." ";
670  $valueStr = !$placeholder ? $mapper->quoteValue($value) : $placeholder;
671  $condition .= $valueStr;
672  }
673  return [$condition, $placeholder];
674  }
675 
676  /**
677  * @see AbstractMapper::loadImpl()
678  */
679  protected function loadImpl(ObjectId $oid, $buildDepth=BuildDepth::SINGLE) {
680  if (self::$isDebugEnabled) {
681  self::$logger->debug("Load object: ".$oid->__toString());
682  }
683  // delegate to loadObjects
684  $criteria = $this->createPKCondition($oid);
685  $pagingInfo = new PagingInfo(1, true);
686  $objects = $this->loadObjects($oid->getType(), $buildDepth, $criteria, null, $pagingInfo);
687  if (sizeof($objects) > 0) {
688  return $objects[0];
689  }
690  else {
691  return null;
692  }
693  }
694 
695  /**
696  * @see AbstractMapper::createImpl()
697  * @note The type parameter is not used here because this class only constructs one type
698  */
699  protected function createImpl($type, $buildDepth=BuildDepth::SINGLE) {
700  if ($buildDepth < 0 && !in_array($buildDepth, [BuildDepth::SINGLE, BuildDepth::REQUIRED])) {
701  throw new IllegalArgumentException("Build depth not supported: $buildDepth");
702  }
703  // create the object
704  $object = $this->createObjectFromData([]);
705 
706  // recalculate build depth for the next generation
707  $newBuildDepth = $buildDepth;
708  if ($buildDepth != BuildDepth::REQUIRED && $buildDepth != BuildDepth::SINGLE && $buildDepth > 0) {
709  $newBuildDepth = $buildDepth-1;
710  }
711 
712  // prevent infinite recursion
713  if ($buildDepth < BuildDepth::MAX) {
714  $relationDescs = $this->getRelations();
715 
716  // set dependend objects of this object
717  foreach ($relationDescs as $curRelationDesc) {
718  if ( ($curRelationDesc->getHierarchyType() == 'child' && ($buildDepth > 0 ||
719  // if BuildDepth::REQUIRED only construct shared/composite children with min multiplicity > 0
720  ($buildDepth == BuildDepth::REQUIRED && $curRelationDesc->getOtherMinMultiplicity() > 0 && $curRelationDesc->getOtherAggregationKind() != 'none')
721  )) ) {
722  $childObject = null;
723  if ($curRelationDesc instanceof RDBManyToManyRelationDescription) {
724  $childObject = $this->persistenceFacade->create($curRelationDesc->getOtherType(), BuildDepth::SINGLE);
725  }
726  else {
727  $childObject = $this->persistenceFacade->create($curRelationDesc->getOtherType(), $newBuildDepth);
728  }
729  $object->setValue($curRelationDesc->getOtherRole(), [$childObject], true, false);
730  }
731  }
732  }
733  return $object;
734  }
735 
736  /**
737  * @see AbstractMapper::saveImpl()
738  */
739  protected function saveImpl(PersistentObject $object) {
740  if ($this->adapter == null) {
741  $this->connect();
742  }
743 
744  // set all missing attributes
745  $this->prepareForStorage($object);
746 
747  if ($object->getState() == PersistentObject::STATE_NEW) {
748  // insert new object
749  $operations = $this->getInsertSQL($object);
750  foreach($operations as $operation) {
751  $mapper = self::getMapper($operation->getType());
752  $mapper->executeOperation($operation);
753  }
754  // log action
755  $this->logAction($object);
756  }
757  else if ($object->getState() == PersistentObject::STATE_DIRTY) {
758  // save existing object
759  // precondition: the object exists in the database
760 
761  // log action
762  $this->logAction($object);
763 
764  // save object
765  $operations = $this->getUpdateSQL($object);
766  foreach($operations as $operation) {
767  $mapper = self::getMapper($operation->getType());
768  $mapper->executeOperation($operation);
769  }
770  }
771 
773 
774  // postcondition: the object is saved to the db
775  // the object state is STATE_CLEAN
776  // attributes are only inserted if their values differ from ''
777  return true;
778  }
779 
780  /**
781  * @see AbstractMapper::deleteImpl()
782  */
783  protected function deleteImpl(PersistentObject $object) {
784  if ($this->adapter == null) {
785  $this->connect();
786  }
787 
788  // log action
789  $this->logAction($object);
790 
791  // delete object
792  $oid = $object->getOID();
793  $affectedRows = 0;
794  $operations = $this->getDeleteSQL($oid);
795  foreach($operations as $operation) {
796  $mapper = self::getMapper($operation->getType());
797  $affectedRows += $mapper->executeOperation($operation);
798  }
799  // only delete children if the object was deleted
800  if ($affectedRows > 0) {
801  $proxy = new PersistentObjectProxy($oid);
802  $relationDescs = $this->getRelations('child');
803  foreach($relationDescs as $relationDesc) {
804  $isManyToMany = ($relationDesc instanceof RDBManyToManyRelationDescription);
805  $isComposite = ($relationDesc->getOtherAggregationKind() == 'composite' ||
806  $isManyToMany);
807  if ($isManyToMany) {
808  // in a many to many relation we only use the relation description
809  // that points to relation objects
810  $relationDesc = $relationDesc->getThisEndRelation();
811  }
812 
813  // load related objects
814  $otherType = $relationDesc->getOtherType();
815  $otherMapper = self::getMapper($otherType, false);
816  $allObjects = $this->loadRelationImpl([$proxy], $relationDesc->getOtherRole());
817  $oidStr = $proxy->getOID()->__toString();
818  if (isset($allObjects[$oidStr])) {
819  foreach($allObjects[$oidStr] as $object) {
820  if ($isManyToMany) {
821  // delete the many to many object immediatly
822  $otherMapper->delete($object);
823  }
824  elseif ($isComposite) {
825  // delete composite and relation object children
826  $object->delete();
827  }
828  else {
829  // unlink shared children
830  $object->setValue($relationDesc->getThisRole(), null, true, false);
831  }
832  }
833  }
834  }
835  }
836  // postcondition: the object and all dependend objects are deleted from db
837  return true;
838  }
839 
840  /**
841  * @see AbstractMapper::getOIDsImpl()
842  * @note The type parameter is not used here because this class only constructs one type
843  */
844  protected function getOIDsImpl($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
845  if ($this->adapter == null) {
846  $this->connect();
847  }
848  $oids = [];
849 
850  // create query
851  $selectStmt = $this->getSelectSQL($criteria, null, $this->getPkNames(), $orderby, $pagingInfo);
852  $data = $this->select($selectStmt, $pagingInfo);
853  if (sizeof($data) == 0) {
854  return $oids;
855  }
856 
857  // collect oids
858  for ($i=0, $count=sizeof($data); $i<$count; $i++) {
859  $oids[] = $this->constructOID($data[$i]);
860  }
861  return $oids;
862  }
863 
864  /**
865  * @see AbstractMapper::loadObjectsImpl()
866  */
867  protected function loadObjectsImpl($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
868  if (self::$isDebugEnabled) {
869  self::$logger->debug("Load objects: ".$type);
870  }
871  $objects = $this->loadObjectsFromQueryParts($type, $buildDepth, $criteria, $orderby, $pagingInfo);
872  return $objects;
873  }
874 
875  /**
876  * Load objects defined by several query parts.
877  * @param $type The type of the object
878  * @param $buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
879  * (except BuildDepth::REQUIRED, BuildDepth::PROXIES_ONLY) (default: BuildDepth::SINGLE)
880  * @param $criteria An array of Criteria instances that define conditions on the type's attributes (optional, default: _null_)
881  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (optional, default: _null_)
882  * @param $pagingInfo A reference PagingInfo instance (optional, default: _null_)
883  * @return Array of PersistentObject instances
884  */
885  protected function loadObjectsFromQueryParts($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
886  if ($buildDepth < 0 && !in_array($buildDepth, [BuildDepth::INFINITE, BuildDepth::SINGLE])) {
887  throw new IllegalArgumentException("Build depth not supported: $buildDepth");
888  }
889 
890  // create query
891  $selectStmt = $this->getSelectSQL($criteria, null, null, $orderby, $pagingInfo);
892 
893  $objects = $this->loadObjectsFromSQL($selectStmt, $buildDepth, $pagingInfo);
894  return $objects;
895  }
896 
897  /**
898  * @see RDBMapper::loadObjectsFromSQL()
899  */
900  public function loadObjectsFromSQL(SelectStatement $selectStmt, $buildDepth=BuildDepth::SINGLE, PagingInfo $pagingInfo=null,
901  &$originalData=null) {
902  if ($this->adapter == null) {
903  $this->connect();
904  }
905  $objects = [];
906 
907  $data = $this->select($selectStmt, $pagingInfo);
908  if (sizeof($data) == 0) {
909  return $objects;
910  }
911 
912  if ($originalData !== null) {
913  $originalData = $data;
914  }
915 
916  $tx = $this->persistenceFacade->getTransaction();
917  for ($i=0, $count=sizeof($data); $i<$count; $i++) {
918  // create the object
919  $object = $this->createObjectFromData($data[$i]);
920 
921  // don't set the state recursive, because otherwise relations would be initialized
922  $object->setState(PersistentObject::STATE_CLEAN);
923 
924  $objects[] = $object;
925  }
926 
927  // attach objects to the transaction
928  $attachedObjects = [];
929  for ($i=0, $count=sizeof($objects); $i<$count; $i++) {
930  $attachedObject = $tx->attach($objects[$i]);
931  // don't return objects that are to be deleted by the current transaction
932  if ($attachedObject->getState() != PersistentObject::STATE_DELETED) {
933  $attachedObjects[] = $attachedObject;
934  }
935  }
936 
937  // add related objects
938  // NOTE: This has to be done after registering the objects with the
939  // transaction to avoid incomplete objects in case of recursive model dependencies
940  $this->addRelatedObjects($attachedObjects, $buildDepth);
941 
942  return $attachedObjects;
943  }
944 
945  /**
946  * Create an object of the mapper's type with the given attributes from the given data
947  * @param $data An associative array with the attribute names as keys and the attribute values as values
948  * @return PersistentObject
949  */
950  protected function createObjectFromData(array $data) {
951  // determine if we are loading or creating
952  $createFromLoadedData = (sizeof($data) > 0) ? true : false;
953 
954  // initialize data and oid
955  $oid = null;
956  $initialData = $data;
957  if ($createFromLoadedData) {
958  $oid = $this->constructOID($initialData);
959  // cleanup data
960  foreach($initialData as $name => $value) {
961  if ($this->hasAttribute($name) || strpos($name, self::INTERNAL_VALUE_PREFIX) === 0) {
962  $value = $this->convertValueFromStorage($name, $value);
963  $initialData[$name] = $value;
964  }
965  else {
966  unset($initialData[$name]);
967  }
968  }
969  }
970 
971  // construct object
972  $object = $this->createObject($oid, $initialData);
973  return $object;
974  }
975 
976  /**
977  * Convert value after retrieval from storage
978  * @param $valueName
979  * @param $value
980  * @return Mixed
981  */
982  protected function convertValueFromStorage($valueName, $value) {
983  // filter values according to type
984  if ($this->hasAttribute($valueName)) {
985  $type = $this->getAttribute($valueName)->getType();
986  // integer
987  if (strpos(strtolower($type), 'int') === 0) {
988  $value = (strlen($value) == 0) ? null : intval($value);
989  }
990  }
991  return $value;
992  }
993 
994  /**
995  * Append the child data to a list of object. If the buildDepth does not determine to load a
996  * child generation, only the oids of the children will be loaded.
997  * @param $objects Array of PersistentObject instances to append the children to
998  * @param $buildDepth @see PersistenceFacade::loadObjects()
999  */
1000  protected function addRelatedObjects(array $objects, $buildDepth=BuildDepth::SINGLE) {
1001  // recalculate build depth for the next generation
1002  $newBuildDepth = $buildDepth;
1003  if ($buildDepth != BuildDepth::SINGLE && $buildDepth != BuildDepth::INFINITE && $buildDepth > 0) {
1004  $newBuildDepth = $buildDepth-1;
1005  }
1006  $loadNextGeneration = (($buildDepth != BuildDepth::SINGLE) && ($buildDepth > 0 || $buildDepth == BuildDepth::INFINITE));
1007 
1008  // get dependend objects of this object
1009  $relationDescs = $this->getRelations();
1010  foreach($relationDescs as $relationDesc) {
1011  $role = $relationDesc->getOtherRole();
1012 
1013  $relationId = $role.$relationDesc->getThisRole();
1014  // if the build depth is not satisfied already and the relation is not
1015  // currently loading, we load the complete objects and add them
1016  if ($loadNextGeneration && !isset($this->loadingRelations[$relationId])) {
1017  $this->loadingRelations[$relationId] = true;
1018  $relatives = $this->loadRelation($objects, $role, $newBuildDepth);
1019  // set the values
1020  foreach ($objects as $object) {
1021  $oidStr = $object->getOID()->__toString();
1022  $object->setValue($role, isset($relatives[$oidStr]) ? $relatives[$oidStr] : null, true, false);
1023  }
1024  unset($this->loadingRelations[$relationId]);
1025  }
1026  // otherwise set the value to not initialized.
1027  // the Node will initialize it with the proxies for the relation objects
1028  // on first access
1029  else {
1030  foreach ($objects as $object) {
1031  if ($object instanceof Node) {
1032  $object->addRelation($role);
1033  }
1034  }
1035  }
1036  }
1037  }
1038 
1039  /**
1040  * @see AbstractMapper::loadRelationImpl()
1041  */
1042  protected function loadRelationImpl(array $objects, $role, $buildDepth=BuildDepth::SINGLE,
1043  $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
1044  if (self::$isDebugEnabled) {
1045  self::$logger->debug("Load relation: ".$role);
1046  }
1047  $relatives = [];
1048  if (sizeof($objects) == 0) {
1049  return $relatives;
1050  }
1051 
1052  $otherRelationDescription = $this->getRelationIncludingNM($role);
1053  if ($otherRelationDescription->getOtherNavigability() == true) {
1054  $otherType = $otherRelationDescription->getOtherType();
1055  $otherMapper = self::getMapper($otherType);
1056 
1057  // load related objects from other mapper
1058  $relatedObjects = [];
1059  $thisRole = $otherRelationDescription->getThisRole();
1060  $thisRelationDescription = $otherMapper->getRelationIncludingNM($thisRole);
1061  if ($thisRelationDescription->getOtherNavigability() == true) {
1062  list($selectStmt, $objValueName, $relValueName) = $otherMapper->getRelationSelectSQL($objects, $thisRole, $criteria, $orderby, $pagingInfo);
1063  $originalData = [];
1064  $relatedObjects = $otherMapper->loadObjectsFromSQL($selectStmt, ($buildDepth == BuildDepth::PROXIES_ONLY) ? BuildDepth::SINGLE : $buildDepth, $pagingInfo, $originalData);
1065  }
1066  }
1067  // group relatedObjects by original objects
1068  $relativeMap = [];
1069  foreach ($relatedObjects as $i => $relatedObject) {
1070  // NOTE: we take the key from the original data, because the corresponding values in the objects might be
1071  // all the same, if the same object is related to multiple objects in a many to many relation
1072  // (because only the first related object was attached to the transaction)
1073  $key = $originalData[$i][$relValueName];
1074  if (!isset($relativeMap[$key])) {
1075  $relativeMap[$key] = [];
1076  }
1077  $relativeMap[$key][] = ($buildDepth != BuildDepth::PROXIES_ONLY) ? $relatedObject :
1078  new PersistentObjectProxy($relatedObject->getOID());
1079 
1080  // remove internal value after use (important when loading nm relations,
1081  // because if not done, the value will not be updated when loading the relation
1082  // for another object, leading to less objects seen in the relation)
1083  if (strpos($relValueName, self::INTERNAL_VALUE_PREFIX) === 0) {
1084  $relatedObject->removeValue($relValueName);
1085  }
1086  }
1087  foreach ($objects as $object) {
1088  $oidStr = $object->getOID()->__toString();
1089  $key = $object->getValue($objValueName);
1090  $relatives[$oidStr] = isset($relativeMap[$key]) ? $relativeMap[$key] : [];
1091  }
1092  return $relatives;
1093  }
1094 
1095  /**
1096  * Handle an event triggered from the laminas db layer
1097  * @param $e
1098  */
1099  public function handleDbEvent($e) {
1100  $statement = $e->getParam('statement', null);
1101  if ($statement != null) {
1102  $this->statements[] = [$statement->getSql(), $statement->getParameterContainer()->getNamedArray()];
1103  }
1104  }
1105 
1106  /**
1107  * @see PersistenceMapper::beginTransaction()
1108  * Since all RDBMapper instances with the same connection parameters share
1109  * one connection, the call will be ignored, if the method was already called
1110  * for another instance.
1111  */
1112  public function beginTransaction() {
1113  if ($this->adapter == null) {
1114  $this->connect();
1115  }
1116  if (!$this->isInTransaction() && ($this->isFileDB || $this != $this->getSequenceMapper())) {
1117  $this->getConnection()->beginTransaction();
1118  $this->setIsInTransaction(true);
1119  }
1120  $this->statements = [];
1121  }
1122 
1123  /**
1124  * @see PersistenceMapper::commitTransaction()
1125  * Since all RDBMapper instances with the same connection parameters share
1126  * one connection, the call will be ignored, if the method was already called
1127  * for another instance.
1128  */
1129  public function commitTransaction() {
1130  if ($this->adapter == null) {
1131  $this->connect();
1132  }
1133  if ($this->isInTransaction() && ($this->isFileDB || $this != $this->getSequenceMapper())) {
1134  $this->getConnection()->commit();
1135  $this->setIsInTransaction(false);
1136  }
1137  }
1138 
1139  /**
1140  * @see PersistenceMapper::rollbackTransaction()
1141  * @note Rollbacks have to be supported by the database.
1142  * Since all RDBMapper instances with the same connection parameters share
1143  * one connection, the call will be ignored, if the method was already called
1144  * for another instance.
1145  */
1146  public function rollbackTransaction() {
1147  if ($this->adapter == null) {
1148  $this->connect();
1149  }
1150  if ($this->isInTransaction() && ($this->isFileDB || $this != $this->getSequenceMapper())) {
1151  $this->getConnection()->rollBack();
1152  $this->setIsInTransaction(false);
1153  }
1154  }
1155 
1156  /**
1157  * @see PersistenceMapper::getStatements()
1158  * This method an array of arrays with the first item being the sql string and the second the bind parameter array
1159  */
1160  public function getStatements() {
1161  return $this->statements;
1162  }
1163 
1164  /**
1165  * Set the transaction state for the connection
1166  * @param $isInTransaction Boolean whether the connection is in a transaction or not
1167  */
1168  protected function setIsInTransaction($isInTransaction) {
1169  self::$inTransaction[$this->connId] = $isInTransaction;
1170  }
1171 
1172  /**
1173  * Check if the connection is currently in a transaction
1174  * @return Boolean
1175  */
1176  protected function isInTransaction() {
1177  return isset(self::$inTransaction[$this->connId]) && self::$inTransaction[$this->connId] === true;
1178  }
1179 
1180  /**
1181  * TEMPLATE METHODS
1182  * Subclasses must implement this method to define their object type.
1183  */
1184 
1185  /**
1186  * Get the names of the attributes in the mapped class to order by default and the sort directions
1187  * (ASC or DESC). The roleName parameter allows to ask for the order with respect to a specific role.
1188  * @param $roleName The role name of the relation (optional, default: _null_)
1189  * @return An array of assciative arrays with the keys sortFieldName and sortDirection (ASC or DESC)
1190  */
1191  abstract protected function getOwnDefaultOrder($roleName=null);
1192 
1193  /**
1194  * Get a list of all RelationDescriptions.
1195  * @return An associative array with the relation names as keys and the RelationDescription instances as values.
1196  */
1197  abstract protected function getRelationDescriptions();
1198 
1199  /**
1200  * Get a list of all AttributeDescriptions.
1201  * @return An associative array with the attribute names as keys and the AttributeDescription instances as values.
1202  */
1203  abstract protected function getAttributeDescriptions();
1204 
1205  /**
1206  * Factory method for the supported object type.
1207  * @param $oid The object id (maybe null)
1208  * @return PersitentObject
1209  */
1210  abstract protected function createObject(ObjectId $oid=null);
1211 
1212  /**
1213  * Set the object primary key and foreign key values for storing the object in the database.
1214  * @param $object PersistentObject instance to insert.
1215  * @note The object does not have the final object id set. If a new id value for a primary key column is needed.
1216  * @note The prepared object will be used in the application afterwards. So values that are only to be modified for
1217  * the storage process should be changed in getInsertSQL() and getUpdateSQL() only!
1218  * for the insert statement, use RDBMapper::getNextId().
1219  */
1220  abstract protected function prepareForStorage(PersistentObject $object);
1221 
1222  /**
1223  * Get the SQL command to select object data from the database.
1224  * @param $criteria An array of Criteria instances that define conditions on the type's attributes (optional, default: _null_)
1225  * @param $alias The alias for the table name (default: _null_)
1226  * @param $attributes An array holding names of attributes to select (optional, default: _null_)
1227  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (optional, default: _null_)
1228  * @param $pagingInfo An PagingInfo instance describing which page to load (optional, default: _null_))
1229  * @param $queryId Identifier for the query cache (maybe null to let implementers handle it). (default: _null_)
1230  * @return SelectStatement instance that selects all object data that match the condition or an array with the query parts.
1231  * @note The names of the data item columns MUST match the data item names provided in the '_datadef' array from RDBMapper::getObjectDefinition()
1232  * Use alias names if not! The selected data will be put into the '_data' array of the object definition.
1233  */
1234  abstract public function getSelectSQL($criteria=null, $alias=null, $attributes=null, $orderby=null, PagingInfo $pagingInfo=null, $queryId=null);
1235 
1236  /**
1237  * Get the SQL command to select those objects from the database that are related to the given object.
1238  * @note Navigability may not be checked in this method
1239  * @note In case of a sortable many to many relation, the sortkey value must also be selected
1240  * @param $otherObjectProxies Array of PersistentObjectProxy instances for the objects to load the relatives for.
1241  * @param $otherRole The role of the other object in relation to the objects to load.
1242  * @param $criteria An array of Criteria instances that define conditions on the object's attributes (optional, default: _null_)
1243  * @param $orderby An array holding names of attributes to order by, maybe appended with 'ASC', 'DESC' (optional, default: _null_)
1244  * @param $pagingInfo An PagingInfo instance describing which page to load (optional, default: _null_)
1245  * @return Array with SelectStatement instance and the attribute names which establish the relation between
1246  * the loaded objects and the proxies (proxies's attribute name first)
1247  */
1248  abstract protected function getRelationSelectSQL(array $otherObjectProxies, $otherRole,
1249  $criteria=null, $orderby=null, PagingInfo $pagingInfo=null);
1250 
1251  /**
1252  * Get the SQL command to insert a object into the database.
1253  * @param $object PersistentObject instance to insert.
1254  * @return Array of PersistenceOperation instances that insert a new object.
1255  */
1256  abstract protected function getInsertSQL(PersistentObject $object);
1257 
1258  /**
1259  * Get the SQL command to update a object in the database.
1260  * @param $object PersistentObject instance to update.
1261  * @return Array of PersistenceOperation instances that update an existing object.
1262  */
1263  abstract protected function getUpdateSQL(PersistentObject $object);
1264 
1265  /**
1266  * Get the SQL command to delete a object from the database.
1267  * @param $oid The object id of the object to delete.
1268  * @return Array of PersistenceOperation instances that delete an existing object.
1269  */
1270  abstract protected function getDeleteSQL(ObjectId $oid);
1271 
1272  /**
1273  * Create an array of condition Criteria instances for the primary key values
1274  * @param $oid The object id that defines the primary key values
1275  * @return Array of Criteria instances
1276  */
1277  abstract protected function createPKCondition(ObjectId $oid);
1278 
1279  /**
1280  * Get the name of the database table, where this type is mapped to
1281  * @return String
1282  */
1283  abstract protected function getTableName();
1284 
1285  /**
1286  * Determine if an attribute is a foreign key
1287  * @return Boolean
1288  */
1289  abstract public function isForeignKey($name);
1290 }
1291 ?>
setIsInTransaction($isInTransaction)
Set the transaction state for the connection.
convertValueFromStorage($valueName, $value)
Convert value after retrieval from storage.
RDBMapper defines the interface for mapper classes that map to relational databases.
Definition: RDBMapper.php:24
executeOperation(PersistenceOperation $operation)
DeleteOperation holds data necessary to accomplish an delete operation on the persistent store.
AbstractRDBMapper maps objects of one type to a relational database schema.
loadObjects($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
wCMF - wemove Content Management Framework Copyright (C) 2005-2020 wemove digital solutions GmbH
handleDbEvent($e)
Handle an event triggered from the laminas db layer.
getParameters($stripColons=false)
Get the select parameters.
A PersistenceOperation instance holds data necessary to accomplish an operation on the persistent sto...
getValue()
Get the value to compare the object with.
Definition: Criteria.php:124
InsertOperation holds data necessary to accomplish an insert operation on the persistent store.
EventManager is responsible for dispatching events to registered listeners.
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.
getRowCount()
Execute a count query and return the row count.
createObject(ObjectId $oid=null)
Factory method for the supported object type.
getUpdateSQL(PersistentObject $object)
Get the SQL command to update a object in the database.
loadObjectsFromSQL(SelectStatement $selectStmt, $buildDepth=BuildDepth::SINGLE, PagingInfo $pagingInfo=null, &$originalData=null)
__construct(PersistenceFacade $persistenceFacade, PermissionManager $permissionManager, ConcurrencyManager $concurrencyManager, EventManager $eventManager)
Constructor.
UpdateOperation instances hold data necessary to accomplish an update operation on the persistent sto...
getType()
Get the entity type that this mapper handles.
PersistenceException signals an exception in the persistence service.
logAction(PersistentObject $obj)
Log the state of the given object.
getTableName()
Get the name of the database table, where this type is mapped to.
getOID()
Get the object id of the PersistentObject.
select(SelectStatement $selectStmt, PagingInfo $pagingInfo=null)
getDeleteSQL(ObjectId $oid)
Get the SQL command to delete a object from the database.
save()
Put the statement into the cache.
createPKCondition(ObjectId $oid)
Create an array of condition Criteria instances for the primary key values.
__toString()
Get a string representation of the object id.
Definition: ObjectId.php:214
getAttribute()
Get the name of the attribute.
Definition: Criteria.php:90
getPkNames()
Get the names of the primary key values.
IllegalArgumentException signals an exception in method arguments.
PersistenceMapper defines the interface for all mapper classes.
renderCriteria(Criteria $criteria, $placeholder=null, $tableName=null, $columnName=null)
setState($state)
Set the state of the object to one of the STATE constants.
prepareForStorage(PersistentObject $object)
Set the object primary key and foreign key values for storing the object in the database.
isForeignKey($name)
Determine if an attribute is a foreign key.
Criteria defines a condition on a PersistentObject's attribute used to select specific instances.
Definition: Criteria.php:21
isInTransaction()
Check if the connection is currently in a transaction.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
BuildDepth values are used to define the depth when loading object trees.
Definition: BuildDepth.php:19
setConnectionParams($params)
Set the connection parameters.
loadObjectsImpl($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
getType()
Get the PersistentObject type that has the attribute.
Definition: Criteria.php:74
isPkValue($name)
Check if a value is a primary key value.
getOwnDefaultOrder($roleName=null)
TEMPLATE METHODS Subclasses must implement this method to define their object type.
PersistenceFacade defines the interface for PersistenceFacade implementations.
getType()
Get the type (including namespace)
Definition: ObjectId.php:96
loadImpl(ObjectId $oid, $buildDepth=BuildDepth::SINGLE)
createObjectFromData(array $data)
Create an object of the mapper's type with the given attributes from the given data.
getNextId()
Get a new id for inserting into the database.
loadObjectsFromQueryParts($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Load objects defined by several query parts.
ConcurrencyManager is used to handle concurrency for objects.
createImpl($type, $buildDepth=BuildDepth::SINGLE)
getAttributeDescriptions()
Get a list of all AttributeDescriptions.
getType()
Get the type of PersistentObject on which the operation should be executed.
FileUtil provides basic support for file functionality like HTTP file upload.
Definition: FileUtil.php:22
loadRelation(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:37
Node adds the concept of relations to PersistentObject.
Definition: Node.php:34
static getInstance($name, $dynamicConfiguration=[])
getRelationSelectSQL(array $otherObjectProxies, $otherRole, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Get the SQL command to select those objects from the database that are related to the given object.
__sleep()
Select data to be stored in the session.
getAttributes(array $tags=[], $matchMode='all')
AbstractMapper provides a basic implementation for other mapper classes.
PersistentObjectProxy is proxy for an PersistentObject instance.
getSelectSQL($criteria=null, $alias=null, $attributes=null, $orderby=null, PagingInfo $pagingInfo=null, $queryId=null)
Get the SQL command to select object data from the database.
getOIDsImpl($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
PersistentObject defines the interface of all persistent objects.
addRelatedObjects(array $objects, $buildDepth=BuildDepth::SINGLE)
Append the child data to a list of object.
getState()
Get the object's state:
PermissionManager implementations are used to handle all authorization requests.
executeSql($sql, $parameters=[])
Execute a query on the connection.
LogManager is used to retrieve Logger instances.
Definition: LogManager.php:20
static realpath($path)
Realpath function that also works for non existing paths code from http://www.php....
Definition: FileUtil.php:244
getInsertSQL(PersistentObject $object)
Get the SQL command to insert a object into the database.
loadRelationImpl(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...
getRelationIncludingNM($roleName)
Implementation of PersistenceMapper::getRelation() including nm classes in many to many relations.
getOperator()
Get the comparison operator used to compare the given value with the attribute's value.
Definition: Criteria.php:108
getRelationDescriptions()
Get a list of all RelationDescriptions.
getConnectionParams()
Get the connection parameters.