TYPO3 CMS  TYPO3_8-7
DataMapper.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
22 
27 {
31  protected $reflectionService;
32 
36  protected $qomFactory;
37 
42 
48  protected $pageSelectObject;
49 
55  protected $dataMaps = [];
56 
60  protected $dataMapFactory;
61 
65  protected $queryFactory;
66 
70  protected $objectManager;
71 
76 
80  public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
81  {
82  $this->reflectionService = $reflectionService;
83  }
84 
88  public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
89  {
90  $this->qomFactory = $qomFactory;
91  }
92 
96  public function injectPersistenceSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession)
97  {
98  $this->persistenceSession = $persistenceSession;
99  }
100 
104  public function injectDataMapFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory)
105  {
106  $this->dataMapFactory = $dataMapFactory;
107  }
108 
112  public function injectQueryFactory(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory)
113  {
114  $this->queryFactory = $queryFactory;
115  }
116 
120  public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
121  {
122  $this->objectManager = $objectManager;
123  }
124 
128  public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
129  {
130  $this->signalSlotDispatcher = $signalSlotDispatcher;
131  }
132 
140  public function map($className, array $rows)
141  {
142  $objects = [];
143  foreach ($rows as $row) {
144  $objects[] = $this->mapSingleRow($this->getTargetType($className, $row), $row);
145  }
146  return $objects;
147  }
148 
156  public function getTargetType($className, array $row)
157  {
158  $dataMap = $this->getDataMap($className);
159  $targetType = $className;
160  if ($dataMap->getRecordTypeColumnName() !== null) {
161  foreach ($dataMap->getSubclasses() as $subclassName) {
162  $recordSubtype = $this->getDataMap($subclassName)->getRecordType();
163  if ((string)$row[$dataMap->getRecordTypeColumnName()] === (string)$recordSubtype) {
164  $targetType = $subclassName;
165  break;
166  }
167  }
168  }
169  return $targetType;
170  }
171 
179  protected function mapSingleRow($className, array $row)
180  {
181  if ($this->persistenceSession->hasIdentifier($row['uid'], $className)) {
182  $object = $this->persistenceSession->getObjectByIdentifier($row['uid'], $className);
183  } else {
184  $object = $this->createEmptyObject($className);
185  $this->persistenceSession->registerObject($object, $row['uid']);
186  $this->thawProperties($object, $row);
187  $this->emitAfterMappingSingleRow($object);
188  $object->_memorizeCleanState();
189  $this->persistenceSession->registerReconstitutedEntity($object);
190  }
191  return $object;
192  }
193 
200  {
201  $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterMappingSingleRow', [$object]);
202  }
203 
211  protected function createEmptyObject($className)
212  {
213  // Note: The class_implements() function also invokes autoload to assure that the interfaces
214  // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
215  if (!in_array(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface::class, class_implements($className))) {
216  throw new CannotReconstituteObjectException('Cannot create empty instance of the class "' . $className
217  . '" because it does not implement the TYPO3\\CMS\\Extbase\\DomainObject\\DomainObjectInterface.', 1234386924);
218  }
219  $object = $this->objectManager->getEmptyObject($className);
220  return $object;
221  }
222 
229  protected function thawProperties(DomainObjectInterface $object, array $row)
230  {
231  $className = get_class($object);
232  $classSchema = $this->reflectionService->getClassSchema($className);
233  $dataMap = $this->getDataMap($className);
234  $object->_setProperty('uid', (int)$row['uid']);
235  $object->_setProperty('pid', (int)$row['pid']);
236  $object->_setProperty('_localizedUid', (int)$row['uid']);
237  $object->_setProperty('_versionedUid', (int)$row['uid']);
238  if ($dataMap->getLanguageIdColumnName() !== null) {
239  $object->_setProperty('_languageUid', (int)$row[$dataMap->getLanguageIdColumnName()]);
240  if (isset($row['_LOCALIZED_UID'])) {
241  $object->_setProperty('_localizedUid', (int)$row['_LOCALIZED_UID']);
242  }
243  }
244  if (!empty($row['_ORIG_uid']) && !empty($GLOBALS['TCA'][$dataMap->getTableName()]['ctrl']['versioningWS'])) {
245  $object->_setProperty('_versionedUid', (int)$row['_ORIG_uid']);
246  }
247  $properties = $object->_getProperties();
248  foreach ($properties as $propertyName => $propertyValue) {
249  if (!$dataMap->isPersistableProperty($propertyName)) {
250  continue;
251  }
252  $columnMap = $dataMap->getColumnMap($propertyName);
253  $columnName = $columnMap->getColumnName();
254  $propertyData = $classSchema->getProperty($propertyName);
255  $propertyValue = null;
256  if ($row[$columnName] !== null) {
257  switch ($propertyData['type']) {
258  case 'integer':
259  $propertyValue = (int)$row[$columnName];
260  break;
261  case 'float':
262  $propertyValue = (double)$row[$columnName];
263  break;
264  case 'boolean':
265  $propertyValue = (bool)$row[$columnName];
266  break;
267  case 'string':
268  $propertyValue = (string)$row[$columnName];
269  break;
270  case 'array':
271  // $propertyValue = $this->mapArray($row[$columnName]); // Not supported, yet!
272  break;
273  case 'SplObjectStorage':
274  case \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class:
275  $propertyValue = $this->mapResultToPropertyValue(
276  $object,
277  $propertyName,
278  $this->fetchRelated($object, $propertyName, $row[$columnName])
279  );
280  break;
281  default:
282  if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
283  $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat(), $propertyData['type']);
284  } elseif (TypeHandlingUtility::isCoreType($propertyData['type'])) {
285  $propertyValue = $this->mapCoreType($propertyData['type'], $row[$columnName]);
286  } else {
287  $propertyValue = $this->mapObjectToClassProperty(
288  $object,
289  $propertyName,
290  $row[$columnName]
291  );
292  }
293 
294  }
295  }
296  if ($propertyValue !== null) {
297  $object->_setProperty($propertyName, $propertyValue);
298  }
299  }
300  }
301 
309  protected function mapCoreType($type, $value)
310  {
311  return new $type($value);
312  }
313 
323  protected function mapDateTime($value, $storageFormat = null, $targetType = 'DateTime')
324  {
325  if (empty($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
326  // 0 -> NULL !!!
327  return null;
328  }
329  if ($storageFormat === 'date' || $storageFormat === 'datetime') {
330  // native date/datetime values are stored in UTC
331  $utcTimeZone = new \DateTimeZone('UTC');
332  $utcDateTime = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($targetType, $value, $utcTimeZone);
333  $currentTimeZone = new \DateTimeZone(date_default_timezone_get());
334  return $utcDateTime->setTimezone($currentTimeZone);
335  }
336  // integer timestamps are local server time
337  return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($targetType, date('c', (int)$value));
338  }
339 
349  public function fetchRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = true)
350  {
351  $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
352  if ($enableLazyLoading === true && $propertyMetaData['lazy']) {
353  if ($propertyMetaData['type'] === \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class) {
354  $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage::class, $parentObject, $propertyName, $fieldValue);
355  } else {
356  if (empty($fieldValue)) {
357  $result = null;
358  } else {
359  $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class, $parentObject, $propertyName, $fieldValue);
360  }
361  }
362  } else {
363  $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue);
364  }
365  return $result;
366  }
367 
376  protected function fetchRelatedEager(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
377  {
378  return $fieldValue === '' ? $this->getEmptyRelationValue($parentObject, $propertyName) : $this->getNonEmptyRelationValue($parentObject, $propertyName, $fieldValue);
379  }
380 
386  protected function getEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName)
387  {
388  $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
389  $relatesToOne = $columnMap->getTypeOfRelation() == ColumnMap::RELATION_HAS_ONE;
390  return $relatesToOne ? null : [];
391  }
392 
399  protected function getNonEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName, $fieldValue)
400  {
401  $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
402  return $query->execute();
403  }
404 
413  protected function getPreparedQuery(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
414  {
415  $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
416  $type = $this->getType(get_class($parentObject), $propertyName);
417  $query = $this->queryFactory->create($type);
418  $query->getQuerySettings()->setRespectStoragePage(false);
419  $query->getQuerySettings()->setRespectSysLanguage(false);
420  if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
421  if ($columnMap->getChildSortByFieldName() !== null) {
422  $query->setOrderings([$columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING]);
423  }
424  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
425  $query->setSource($this->getSource($parentObject, $propertyName));
426  if ($columnMap->getChildSortByFieldName() !== null) {
427  $query->setOrderings([$columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING]);
428  }
429  }
430  $query->matching($this->getConstraint($query, $parentObject, $propertyName, $fieldValue, $columnMap->getRelationTableMatchFields()));
431  return $query;
432  }
433 
444  protected function getConstraint(Persistence\QueryInterface $query, DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = [])
445  {
446  $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
447  if ($columnMap->getParentKeyFieldName() !== null) {
448  $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject);
449  if ($columnMap->getParentTableFieldName() !== null) {
450  $constraint = $query->logicalAnd(
451  $constraint,
452  $query->equals($columnMap->getParentTableFieldName(), $this->getDataMap(get_class($parentObject))->getTableName())
453  );
454  }
455  } else {
456  $constraint = $query->in('uid', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $fieldValue));
457  }
458  if (!empty($relationTableMatchFields)) {
459  foreach ($relationTableMatchFields as $relationTableMatchFieldName => $relationTableMatchFieldValue) {
460  $constraint = $query->logicalAnd($constraint, $query->equals($relationTableMatchFieldName, $relationTableMatchFieldValue));
461  }
462  }
463  return $constraint;
464  }
465 
473  protected function getSource(DomainObjectInterface $parentObject, $propertyName)
474  {
475  $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
476  $left = $this->qomFactory->selector(null, $columnMap->getRelationTableName());
477  $childClassName = $this->getType(get_class($parentObject), $propertyName);
478  $right = $this->qomFactory->selector($childClassName, $columnMap->getChildTableName());
479  $joinCondition = $this->qomFactory->equiJoinCondition($columnMap->getRelationTableName(), $columnMap->getChildKeyFieldName(), $columnMap->getChildTableName(), 'uid');
480  $source = $this->qomFactory->join($left, $right, Persistence\Generic\Query::JCR_JOIN_TYPE_INNER, $joinCondition);
481  return $source;
482  }
483 
499  protected function mapObjectToClassProperty(DomainObjectInterface $parentObject, $propertyName, $fieldValue)
500  {
501  if ($this->propertyMapsByForeignKey($parentObject, $propertyName)) {
502  $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
503  $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
504  } else {
505  if ($fieldValue === '') {
506  $propertyValue = $this->getEmptyRelationValue($parentObject, $propertyName);
507  } else {
508  $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
509  if ($this->persistenceSession->hasIdentifier($fieldValue, $propertyMetaData['type'])) {
510  $propertyValue = $this->persistenceSession->getObjectByIdentifier($fieldValue, $propertyMetaData['type']);
511  } else {
512  $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
513  $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
514  }
515  }
516  }
517 
518  return $propertyValue;
519  }
520 
528  protected function propertyMapsByForeignKey(DomainObjectInterface $parentObject, $propertyName)
529  {
530  $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
531  return $columnMap->getParentKeyFieldName() !== null;
532  }
533 
542  public function mapResultToPropertyValue(DomainObjectInterface $parentObject, $propertyName, $result)
543  {
544  $propertyValue = null;
545  if ($result instanceof Persistence\Generic\LoadingStrategyInterface) {
546  $propertyValue = $result;
547  } else {
548  $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
549  if (in_array($propertyMetaData['type'], ['array', 'ArrayObject', 'SplObjectStorage', \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class], true)) {
550  $objects = [];
551  foreach ($result as $value) {
552  $objects[] = $value;
553  }
554  if ($propertyMetaData['type'] === 'ArrayObject') {
555  $propertyValue = new \ArrayObject($objects);
556  } elseif (in_array($propertyMetaData['type'], [\TYPO3\CMS\Extbase\Persistence\ObjectStorage::class], true)) {
557  $propertyValue = new Persistence\ObjectStorage();
558  foreach ($objects as $object) {
559  $propertyValue->attach($object);
560  }
561  $propertyValue->_memorizeCleanState();
562  } else {
563  $propertyValue = $objects;
564  }
565  } elseif (strpbrk($propertyMetaData['type'], '_\\') !== false) {
566  if (is_object($result) && $result instanceof Persistence\QueryResultInterface) {
567  $propertyValue = $result->getFirst();
568  } else {
569  $propertyValue = $result;
570  }
571  }
572  }
573  return $propertyValue;
574  }
575 
584  public function countRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
585  {
586  $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
587  return $query->execute()->count();
588  }
589 
598  public function isPersistableProperty($className, $propertyName)
599  {
600  $dataMap = $this->getDataMap($className);
601  return $dataMap->isPersistableProperty($propertyName);
602  }
603 
611  public function getDataMap($className)
612  {
613  if (!is_string($className) || $className === '') {
614  throw new Persistence\Generic\Exception('No class name was given to retrieve the Data Map for.', 1251315965);
615  }
616  if (!isset($this->dataMaps[$className])) {
617  $this->dataMaps[$className] = $this->dataMapFactory->buildDataMap($className);
618  }
619  return $this->dataMaps[$className];
620  }
621 
628  public function convertClassNameToTableName($className)
629  {
630  return $this->getDataMap($className)->getTableName();
631  }
632 
640  public function convertPropertyNameToColumnName($propertyName, $className = null)
641  {
642  if (!empty($className)) {
643  $dataMap = $this->getDataMap($className);
644  if ($dataMap !== null) {
645  $columnMap = $dataMap->getColumnMap($propertyName);
646  if ($columnMap !== null) {
647  return $columnMap->getColumnName();
648  }
649  }
650  }
651  return \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($propertyName);
652  }
653 
662  public function getType($parentClassName, $propertyName)
663  {
664  $propertyMetaData = $this->reflectionService->getClassSchema($parentClassName)->getProperty($propertyName);
665  if (!empty($propertyMetaData['elementType'])) {
666  $type = $propertyMetaData['elementType'];
667  } elseif (!empty($propertyMetaData['type'])) {
668  $type = $propertyMetaData['type'];
669  } else {
670  throw new UnexpectedTypeException('Could not determine the child object type.', 1251315967);
671  }
672  return $type;
673  }
674 
687  public function getPlainValue($input, $columnMap = null, $parseStringValueCallback = null, array $parseStringValueCallbackParameters = [])
688  {
689  if ($input === null) {
690  return 'NULL';
691  }
692  if ($input instanceof Persistence\Generic\LazyLoadingProxy) {
693  $input = $input->_loadRealInstance();
694  }
695 
696  if (is_bool($input)) {
697  $parameter = (int)$input;
698  } elseif (is_int($input)) {
699  $parameter = $input;
700  } elseif ($input instanceof \DateTime) {
701  if (!is_null($columnMap) && !is_null($columnMap->getDateTimeStorageFormat())) {
702  $storageFormat = $columnMap->getDateTimeStorageFormat();
703  $timeZoneToStore = clone $input;
704  // set to UTC to store in database
705  $timeZoneToStore->setTimezone(new \DateTimeZone('UTC'));
706  switch ($storageFormat) {
707  case 'datetime':
708  $parameter = $timeZoneToStore->format('Y-m-d H:i:s');
709  break;
710  case 'date':
711  $parameter = $timeZoneToStore->format('Y-m-d');
712  break;
713  default:
714  throw new \InvalidArgumentException('Column map DateTime format "' . $storageFormat . '" is unknown. Allowed values are datetime or date.', 1395353470);
715  }
716  } else {
717  $parameter = $input->format('U');
718  }
719  } elseif ($input instanceof DomainObjectInterface) {
720  $parameter = (int)$input->getUid();
722  $plainValueArray = [];
723  foreach ($input as $inputElement) {
724  $plainValueArray[] = $this->getPlainValue($inputElement, $columnMap, $parseStringValueCallback, $parseStringValueCallbackParameters);
725  }
726  $parameter = implode(',', $plainValueArray);
727  } elseif (is_object($input)) {
728  if (TypeHandlingUtility::isCoreType($input)) {
729  $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
730  } else {
731  throw new UnexpectedTypeException('An object of class "' . get_class($input) . '" could not be converted to a plain value.', 1274799934);
732  }
733  } else {
734  $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
735  }
736  return $parameter;
737  }
738 
748  protected function getPlainStringValue($value, $callback = null, array $additionalParameters = [])
749  {
750  if (is_callable($callback)) {
751  $value = call_user_func($callback, $value, $additionalParameters);
752  }
753  return (string)$value;
754  }
755 }
thawProperties(DomainObjectInterface $object, array $row)
Definition: DataMapper.php:229
countRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue='')
Definition: DataMapper.php:584
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
convertPropertyNameToColumnName($propertyName, $className=null)
Definition: DataMapper.php:640
emitAfterMappingSingleRow(DomainObjectInterface $object)
Definition: DataMapper.php:199
injectQueryFactory(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory)
Definition: DataMapper.php:112
getNonEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName, $fieldValue)
Definition: DataMapper.php:399
injectDataMapFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory)
Definition: DataMapper.php:104
injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
Definition: DataMapper.php:128
mapResultToPropertyValue(DomainObjectInterface $parentObject, $propertyName, $result)
Definition: DataMapper.php:542
getPlainValue($input, $columnMap=null, $parseStringValueCallback=null, array $parseStringValueCallbackParameters=[])
Definition: DataMapper.php:687
getPreparedQuery(DomainObjectInterface $parentObject, $propertyName, $fieldValue='')
Definition: DataMapper.php:413
fetchRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue='', $enableLazyLoading=true)
Definition: DataMapper.php:349
injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
Definition: DataMapper.php:80
static makeInstance($className,... $constructorArguments)
fetchRelatedEager(DomainObjectInterface $parentObject, $propertyName, $fieldValue='')
Definition: DataMapper.php:376
mapObjectToClassProperty(DomainObjectInterface $parentObject, $propertyName, $fieldValue)
Definition: DataMapper.php:499
mapDateTime($value, $storageFormat=null, $targetType='DateTime')
Definition: DataMapper.php:323
injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
Definition: DataMapper.php:120
propertyMapsByForeignKey(DomainObjectInterface $parentObject, $propertyName)
Definition: DataMapper.php:528
getSource(DomainObjectInterface $parentObject, $propertyName)
Definition: DataMapper.php:473
getPlainStringValue($value, $callback=null, array $additionalParameters=[])
Definition: DataMapper.php:748
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
getEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName)
Definition: DataMapper.php:386
injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
Definition: DataMapper.php:88
injectPersistenceSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession)
Definition: DataMapper.php:96
getConstraint(Persistence\QueryInterface $query, DomainObjectInterface $parentObject, $propertyName, $fieldValue='', $relationTableMatchFields=[])
Definition: DataMapper.php:444