[BUGFIX] Allow subclasses of \DateTime as entity properties
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Mapper / DataMapper.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
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
17 use TYPO3\CMS\Extbase\Object\Exception\CannotReconstituteObjectException;
18 use TYPO3\CMS\Extbase\Persistence;
19 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
20 use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
21 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
22
23 /**
24 * A mapper to map database tables configured in $TCA on domain objects.
25 */
26 class DataMapper implements \TYPO3\CMS\Core\SingletonInterface {
27
28 /**
29 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
30 */
31 protected $reflectionService;
32
33 /**
34 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
35 */
36 protected $qomFactory;
37
38 /**
39 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Session
40 */
41 protected $persistenceSession;
42
43 /**
44 * A reference to the page select object providing methods to perform language and work space overlays
45 *
46 * @var \TYPO3\CMS\Frontend\Page\PageRepository
47 */
48 protected $pageSelectObject;
49
50 /**
51 * Cached data maps
52 *
53 * @var array
54 */
55 protected $dataMaps = array();
56
57 /**
58 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory
59 */
60 protected $dataMapFactory;
61
62 /**
63 * @var \TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface
64 */
65 protected $queryFactory;
66
67 /**
68 * The TYPO3 reference index object
69 *
70 * @var \TYPO3\CMS\Core\Database\ReferenceIndex
71 */
72 protected $referenceIndex;
73
74 /**
75 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
76 */
77 protected $objectManager;
78
79 /**
80 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
81 */
82 protected $signalSlotDispatcher;
83
84 /**
85 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
86 */
87 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService) {
88 $this->reflectionService = $reflectionService;
89 }
90
91 /**
92 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
93 */
94 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory) {
95 $this->qomFactory = $qomFactory;
96 }
97
98 /**
99 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession
100 */
101 public function injectPersistenceSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession) {
102 $this->persistenceSession = $persistenceSession;
103 }
104
105 /**
106 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory
107 */
108 public function injectDataMapFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory) {
109 $this->dataMapFactory = $dataMapFactory;
110 }
111
112 /**
113 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory
114 */
115 public function injectQueryFactory(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory) {
116 $this->queryFactory = $queryFactory;
117 }
118
119 /**
120 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
121 */
122 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) {
123 $this->objectManager = $objectManager;
124 }
125
126 /**
127 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
128 */
129 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher) {
130 $this->signalSlotDispatcher = $signalSlotDispatcher;
131 }
132
133 /**
134 * Maps the given rows on objects
135 *
136 * @param string $className The name of the class
137 * @param array $rows An array of arrays with field_name => value pairs
138 * @return array An array of objects of the given class
139 */
140 public function map($className, array $rows) {
141 $objects = array();
142 foreach ($rows as $row) {
143 $objects[] = $this->mapSingleRow($this->getTargetType($className, $row), $row);
144 }
145 return $objects;
146 }
147
148 /**
149 * Returns the target type for the given row.
150 *
151 * @param string $className The name of the class
152 * @param array $row A single array with field_name => value pairs
153 * @return string The target type (a class name)
154 */
155 public function getTargetType($className, array $row) {
156 $dataMap = $this->getDataMap($className);
157 $targetType = $className;
158 if ($dataMap->getRecordTypeColumnName() !== NULL) {
159 foreach ($dataMap->getSubclasses() as $subclassName) {
160 $recordSubtype = $this->getDataMap($subclassName)->getRecordType();
161 if ($row[$dataMap->getRecordTypeColumnName()] === $recordSubtype) {
162 $targetType = $subclassName;
163 break;
164 }
165 }
166 }
167 return $targetType;
168 }
169
170 /**
171 * Maps a single row on an object of the given class
172 *
173 * @param string $className The name of the target class
174 * @param array $row A single array with field_name => value pairs
175 * @return object An object of the given class
176 */
177 protected function mapSingleRow($className, array $row) {
178 if ($this->persistenceSession->hasIdentifier($row['uid'], $className)) {
179 $object = $this->persistenceSession->getObjectByIdentifier($row['uid'], $className);
180 } else {
181 $object = $this->createEmptyObject($className);
182 $this->persistenceSession->registerObject($object, $row['uid']);
183 $this->thawProperties($object, $row);
184 $this->emitAfterMappingSingleRow($object);
185 $object->_memorizeCleanState();
186 $this->persistenceSession->registerReconstitutedEntity($object);
187 }
188 return $object;
189 }
190
191 /**
192 * Emits a signal after mapping a single row.
193 *
194 * @param DomainObjectInterface $object The mapped object
195 */
196 protected function emitAfterMappingSingleRow(DomainObjectInterface $object) {
197 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterMappingSingleRow', array($object));
198 }
199
200 /**
201 * Creates a skeleton of the specified object
202 *
203 * @param string $className Name of the class to create a skeleton for
204 * @throws CannotReconstituteObjectException
205 * @return object The object skeleton
206 */
207 protected function createEmptyObject($className) {
208 // Note: The class_implements() function also invokes autoload to assure that the interfaces
209 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
210 if (!in_array(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface::class, class_implements($className))) {
211 throw new CannotReconstituteObjectException('Cannot create empty instance of the class "' . $className
212 . '" because it does not implement the TYPO3\\CMS\\Extbase\\DomainObject\\DomainObjectInterface.', 1234386924);
213 }
214 $object = $this->objectManager->getEmptyObject($className);
215 return $object;
216 }
217
218 /**
219 * Sets the given properties on the object.
220 *
221 * @param DomainObjectInterface $object The object to set properties on
222 * @param array $row
223 * @return void
224 */
225 protected function thawProperties(DomainObjectInterface $object, array $row) {
226 $className = get_class($object);
227 $classSchema = $this->reflectionService->getClassSchema($className);
228 $dataMap = $this->getDataMap($className);
229 $object->_setProperty('uid', (int)$row['uid']);
230 $object->_setProperty('pid', (int)$row['pid']);
231 $object->_setProperty('_localizedUid', (int)$row['uid']);
232 $object->_setProperty('_versionedUid', (int)$row['uid']);
233 if ($dataMap->getLanguageIdColumnName() !== NULL) {
234 $object->_setProperty('_languageUid', (int)$row[$dataMap->getLanguageIdColumnName()]);
235 if (isset($row['_LOCALIZED_UID'])) {
236 $object->_setProperty('_localizedUid', (int)$row['_LOCALIZED_UID']);
237 }
238 }
239 if (!empty($row['_ORIG_uid']) && !empty($GLOBALS['TCA'][$dataMap->getTableName()]['ctrl']['versioningWS'])) {
240 $object->_setProperty('_versionedUid', (int)$row['_ORIG_uid']);
241 }
242 $properties = $object->_getProperties();
243 foreach ($properties as $propertyName => $propertyValue) {
244 if (!$dataMap->isPersistableProperty($propertyName)) {
245 continue;
246 }
247 $columnMap = $dataMap->getColumnMap($propertyName);
248 $columnName = $columnMap->getColumnName();
249 $propertyData = $classSchema->getProperty($propertyName);
250 $propertyValue = NULL;
251 if ($row[$columnName] !== NULL) {
252 switch ($propertyData['type']) {
253 case 'integer':
254 $propertyValue = (int)$row[$columnName];
255 break;
256 case 'float':
257 $propertyValue = (double)$row[$columnName];
258 break;
259 case 'boolean':
260 $propertyValue = (bool)$row[$columnName];
261 break;
262 case 'string':
263 $propertyValue = (string)$row[$columnName];
264 break;
265 case 'array':
266 // $propertyValue = $this->mapArray($row[$columnName]); // Not supported, yet!
267 break;
268 case 'SplObjectStorage':
269 case \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class:
270 $propertyValue = $this->mapResultToPropertyValue(
271 $object,
272 $propertyName,
273 $this->fetchRelated($object, $propertyName, $row[$columnName])
274 );
275 break;
276 default:
277 if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
278 $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat(), $propertyData['type']);
279 } elseif (TypeHandlingUtility::isCoreType($propertyData['type'])) {
280 $propertyValue = $this->mapCoreType($propertyData['type'], $row[$columnName]);
281 } else {
282 $propertyValue = $this->mapObjectToClassProperty(
283 $object,
284 $propertyName,
285 $row[$columnName]
286 );
287 }
288
289 }
290 }
291 if ($propertyValue !== NULL) {
292 $object->_setProperty($propertyName, $propertyValue);
293 }
294 }
295 }
296
297 /**
298 * Map value to a core type
299 *
300 * @param string $type
301 * @param mixed $value
302 * @return \TYPO3\CMS\Core\Type\TypeInterface
303 */
304 protected function mapCoreType($type, $value) {
305 return new $type($value);
306 }
307
308 /**
309 * Creates a DateTime from an unix timestamp or date/datetime value.
310 * If the input is empty, NULL is returned.
311 *
312 * @param int|string $value Unix timestamp or date/datetime value
313 * @param NULL|string $storageFormat Storage format for native date/datetime fields
314 * @param NULL|string $targetType The object class name to be created
315 * @return \DateTime
316 */
317 protected function mapDateTime($value, $storageFormat = NULL, $targetType = 'DateTime') {
318 if (empty($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
319 // 0 -> NULL !!!
320 return NULL;
321 } elseif ($storageFormat === 'date' || $storageFormat === 'datetime') {
322 // native date/datetime values are stored in UTC
323 $utcTimeZone = new \DateTimeZone('UTC');
324 $utcDateTime = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($targetType, $value, $utcTimeZone);
325 $currentTimeZone = new \DateTimeZone(date_default_timezone_get());
326 return $utcDateTime->setTimezone($currentTimeZone);
327 } else {
328 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($targetType, date('c', $value));
329 }
330 }
331
332 /**
333 * Fetches a collection of objects related to a property of a parent object
334 *
335 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
336 * @param string $propertyName The name of the proxied property in it's parent
337 * @param mixed $fieldValue The raw field value.
338 * @param bool $enableLazyLoading A flag indication if the related objects should be lazy loaded
339 * @return \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage|Persistence\QueryResultInterface The result
340 */
341 public function fetchRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = TRUE) {
342 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
343 if ($enableLazyLoading === TRUE && $propertyMetaData['lazy']) {
344 if ($propertyMetaData['type'] === \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class) {
345 $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage::class, $parentObject, $propertyName, $fieldValue);
346 } else {
347 if (empty($fieldValue)) {
348 $result = NULL;
349 } else {
350 $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class, $parentObject, $propertyName, $fieldValue);
351 }
352 }
353 } else {
354 $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue);
355 }
356 return $result;
357 }
358
359 /**
360 * Fetches the related objects from the storage backend.
361 *
362 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
363 * @param string $propertyName The name of the proxied property in it's parent
364 * @param mixed $fieldValue The raw field value.
365 * @return mixed
366 */
367 protected function fetchRelatedEager(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
368 return $fieldValue === '' ? $this->getEmptyRelationValue($parentObject, $propertyName) : $this->getNonEmptyRelationValue($parentObject, $propertyName, $fieldValue);
369 }
370
371 /**
372 * @param DomainObjectInterface $parentObject
373 * @param string $propertyName
374 * @return array|NULL
375 */
376 protected function getEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName) {
377 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
378 $relatesToOne = $columnMap->getTypeOfRelation() == ColumnMap::RELATION_HAS_ONE;
379 return $relatesToOne ? NULL : array();
380 }
381
382 /**
383 * @param DomainObjectInterface $parentObject
384 * @param string $propertyName
385 * @param string $fieldValue
386 * @return Persistence\QueryResultInterface
387 */
388 protected function getNonEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName, $fieldValue) {
389 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
390 return $query->execute();
391 }
392
393 /**
394 * Builds and returns the prepared query, ready to be executed.
395 *
396 * @param DomainObjectInterface $parentObject
397 * @param string $propertyName
398 * @param string $fieldValue
399 * @return Persistence\QueryInterface
400 */
401 protected function getPreparedQuery(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
402 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
403 $type = $this->getType(get_class($parentObject), $propertyName);
404 $query = $this->queryFactory->create($type);
405 $query->getQuerySettings()->setRespectStoragePage(FALSE);
406 $query->getQuerySettings()->setRespectSysLanguage(FALSE);
407 if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
408 if ($columnMap->getChildSortByFieldName() !== NULL) {
409 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING));
410 }
411 } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
412 $query->setSource($this->getSource($parentObject, $propertyName));
413 if ($columnMap->getChildSortByFieldName() !== NULL) {
414 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING));
415 }
416 }
417 $query->matching($this->getConstraint($query, $parentObject, $propertyName, $fieldValue, $columnMap->getRelationTableMatchFields()));
418 return $query;
419 }
420
421 /**
422 * Builds and returns the constraint for multi value properties.
423 *
424 * @param Persistence\QueryInterface $query
425 * @param DomainObjectInterface $parentObject
426 * @param string $propertyName
427 * @param string $fieldValue
428 * @param array $relationTableMatchFields
429 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
430 */
431 protected function getConstraint(Persistence\QueryInterface $query, DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = array()) {
432 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
433 if ($columnMap->getParentKeyFieldName() !== NULL) {
434 $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject);
435 if ($columnMap->getParentTableFieldName() !== NULL) {
436 $constraint = $query->logicalAnd(
437 $constraint,
438 $query->equals($columnMap->getParentTableFieldName(), $this->getDataMap(get_class($parentObject))->getTableName())
439 );
440 }
441 } else {
442 $constraint = $query->in('uid', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $fieldValue));
443 }
444 if (!empty($relationTableMatchFields)) {
445 foreach ($relationTableMatchFields as $relationTableMatchFieldName => $relationTableMatchFieldValue) {
446 $constraint = $query->logicalAnd($constraint, $query->equals($relationTableMatchFieldName, $relationTableMatchFieldValue));
447 }
448 }
449 return $constraint;
450 }
451
452 /**
453 * Builds and returns the source to build a join for a m:n relation.
454 *
455 * @param DomainObjectInterface $parentObject
456 * @param string $propertyName
457 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
458 */
459 protected function getSource(DomainObjectInterface $parentObject, $propertyName) {
460 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
461 $left = $this->qomFactory->selector(NULL, $columnMap->getRelationTableName());
462 $childClassName = $this->getType(get_class($parentObject), $propertyName);
463 $right = $this->qomFactory->selector($childClassName, $columnMap->getChildTableName());
464 $joinCondition = $this->qomFactory->equiJoinCondition($columnMap->getRelationTableName(), $columnMap->getChildKeyFieldName(), $columnMap->getChildTableName(), 'uid');
465 $source = $this->qomFactory->join($left, $right, Persistence\Generic\Query::JCR_JOIN_TYPE_INNER, $joinCondition);
466 return $source;
467 }
468
469 /**
470 * Returns the mapped classProperty from the identiyMap or
471 * mapResultToPropertyValue()
472 *
473 * If the field value is empty and the column map has no parent key field name,
474 * the relation will be empty. If the persistence session has a registered object of
475 * the correct type and identity (fieldValue), this function returns that object.
476 * Otherwise, it proceeds with mapResultToPropertyValue().
477 *
478 * @param DomainObjectInterface $parentObject
479 * @param string $propertyName
480 * @param mixed $fieldValue the raw field value
481 * @return mixed
482 * @see mapResultToPropertyValue()
483 */
484 protected function mapObjectToClassProperty(DomainObjectInterface $parentObject, $propertyName, $fieldValue) {
485 if ($this->propertyMapsByForeignKey($parentObject, $propertyName)) {
486 $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
487 $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
488 } else {
489 if ($fieldValue === '') {
490 $propertyValue = $this->getEmptyRelationValue($parentObject, $propertyName);
491 } else {
492 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
493 if ($this->persistenceSession->hasIdentifier($fieldValue, $propertyMetaData['type'])) {
494 $propertyValue = $this->persistenceSession->getObjectByIdentifier($fieldValue, $propertyMetaData['type']);
495 } else {
496 $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
497 $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
498 }
499 }
500 }
501
502 return $propertyValue;
503 }
504
505 /**
506 * Checks if the relation is based on a foreign key.
507 *
508 * @param DomainObjectInterface $parentObject
509 * @param string $propertyName
510 * @return bool TRUE if the property is mapped
511 */
512 protected function propertyMapsByForeignKey(DomainObjectInterface $parentObject, $propertyName) {
513 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
514 return ($columnMap->getParentKeyFieldName() !== NULL);
515 }
516
517 /**
518 * Returns the given result as property value of the specified property type.
519 *
520 * @param DomainObjectInterface $parentObject
521 * @param string $propertyName
522 * @param mixed $result The result
523 * @return mixed
524 */
525 public function mapResultToPropertyValue(DomainObjectInterface $parentObject, $propertyName, $result) {
526 $propertyValue = NULL;
527 if ($result instanceof Persistence\Generic\LoadingStrategyInterface) {
528 $propertyValue = $result;
529 } else {
530 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
531 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'SplObjectStorage', \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class), TRUE)) {
532 $objects = array();
533 foreach ($result as $value) {
534 $objects[] = $value;
535 }
536 if ($propertyMetaData['type'] === 'ArrayObject') {
537 $propertyValue = new \ArrayObject($objects);
538 } elseif (in_array($propertyMetaData['type'], array(\TYPO3\CMS\Extbase\Persistence\ObjectStorage::class), TRUE)) {
539 $propertyValue = new Persistence\ObjectStorage();
540 foreach ($objects as $object) {
541 $propertyValue->attach($object);
542 }
543 $propertyValue->_memorizeCleanState();
544 } else {
545 $propertyValue = $objects;
546 }
547 } elseif (strpbrk($propertyMetaData['type'], '_\\') !== FALSE) {
548 if (is_object($result) && $result instanceof Persistence\QueryResultInterface) {
549 $propertyValue = $result->getFirst();
550 } else {
551 $propertyValue = $result;
552 }
553 }
554 }
555 return $propertyValue;
556 }
557
558 /**
559 * Counts the number of related objects assigned to a property of a parent object
560 *
561 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
562 * @param string $propertyName The name of the proxied property in it's parent
563 * @param mixed $fieldValue The raw field value.
564 * @return int
565 */
566 public function countRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
567 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
568 return $query->execute()->count();
569 }
570
571 /**
572 * Delegates the call to the Data Map.
573 * Returns TRUE if the property is persistable (configured in $TCA)
574 *
575 * @param string $className The property name
576 * @param string $propertyName The property name
577 * @return bool TRUE if the property is persistable (configured in $TCA)
578 */
579 public function isPersistableProperty($className, $propertyName) {
580 $dataMap = $this->getDataMap($className);
581 return $dataMap->isPersistableProperty($propertyName);
582 }
583
584 /**
585 * Returns a data map for a given class name
586 *
587 * @param string $className The class name you want to fetch the Data Map for
588 * @throws Persistence\Generic\Exception
589 * @return DataMap The data map
590 */
591 public function getDataMap($className) {
592 if (!is_string($className) || $className === '') {
593 throw new Persistence\Generic\Exception('No class name was given to retrieve the Data Map for.', 1251315965);
594 }
595 if (!isset($this->dataMaps[$className])) {
596 $this->dataMaps[$className] = $this->dataMapFactory->buildDataMap($className);
597 }
598 return $this->dataMaps[$className];
599 }
600
601 /**
602 * Returns the selector (table) name for a given class name.
603 *
604 * @param string $className
605 * @return string The selector name
606 */
607 public function convertClassNameToTableName($className) {
608 return $this->getDataMap($className)->getTableName();
609 }
610
611 /**
612 * Returns the column name for a given property name of the specified class.
613 *
614 * @param string $propertyName
615 * @param string $className
616 * @return string The column name
617 */
618 public function convertPropertyNameToColumnName($propertyName, $className = NULL) {
619 if (!empty($className)) {
620 $dataMap = $this->getDataMap($className);
621 if ($dataMap !== NULL) {
622 $columnMap = $dataMap->getColumnMap($propertyName);
623 if ($columnMap !== NULL) {
624 return $columnMap->getColumnName();
625 }
626 }
627 }
628 return \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($propertyName);
629 }
630
631 /**
632 * Returns the type of a child object.
633 *
634 * @param string $parentClassName The class name of the object this proxy is part of
635 * @param string $propertyName The name of the proxied property in it's parent
636 * @throws UnexpectedTypeException
637 * @return string The class name of the child object
638 */
639 public function getType($parentClassName, $propertyName) {
640 $propertyMetaData = $this->reflectionService->getClassSchema($parentClassName)->getProperty($propertyName);
641 if (!empty($propertyMetaData['elementType'])) {
642 $type = $propertyMetaData['elementType'];
643 } elseif (!empty($propertyMetaData['type'])) {
644 $type = $propertyMetaData['type'];
645 } else {
646 throw new UnexpectedTypeException('Could not determine the child object type.', 1251315967);
647 }
648 return $type;
649 }
650
651 /**
652 * Returns a plain value, i.e. objects are flattened out if possible.
653 * Multi value objects or arrays will be converted to a comma-separated list for use in IN SQL queries.
654 *
655 * @param mixed $input The value that will be converted.
656 * @param ColumnMap $columnMap Optional column map for retrieving the date storage format.
657 * @param callable $parseStringValueCallback Optional callback method that will be called for string values. Can be used to do database quotation.
658 * @param array $parseStringValueCallbackParameters Additional parameters that will be passed to the callabck as second parameter.
659 * @throws \InvalidArgumentException
660 * @throws UnexpectedTypeException
661 * @return int|string
662 */
663 public function getPlainValue($input, $columnMap = NULL, $parseStringValueCallback = NULL, array $parseStringValueCallbackParameters = array()) {
664 if ($input === NULL) {
665 return 'NULL';
666 }
667 if ($input instanceof Persistence\Generic\LazyLoadingProxy) {
668 $input = $input->_loadRealInstance();
669 }
670
671 if (is_bool($input)) {
672 $parameter = (int)$input;
673 } elseif ($input instanceof \DateTime) {
674 if (!is_null($columnMap) && !is_null($columnMap->getDateTimeStorageFormat())) {
675 $storageFormat = $columnMap->getDateTimeStorageFormat();
676 switch ($storageFormat) {
677 case 'datetime':
678 $parameter = $input->format('Y-m-d H:i:s');
679 break;
680 case 'date':
681 $parameter = $input->format('Y-m-d');
682 break;
683 default:
684 throw new \InvalidArgumentException('Column map DateTime format "' . $storageFormat . '" is unknown. Allowed values are datetime or date.', 1395353470);
685 }
686 } else {
687 $parameter = $input->format('U');
688 }
689 } elseif (TypeHandlingUtility::isValidTypeForMultiValueComparison($input)) {
690 $plainValueArray = array();
691 foreach ($input as $inputElement) {
692 $plainValueArray[] = $this->getPlainValue($inputElement, $columnMap, $parseStringValueCallback, $parseStringValueCallbackParameters);
693 }
694 $parameter = implode(',', $plainValueArray);
695 } elseif ($input instanceof DomainObjectInterface) {
696 $parameter = (int)$input->getUid();
697 } elseif (is_object($input)) {
698 if (TypeHandlingUtility::isCoreType($input)) {
699 $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
700 } else {
701 throw new UnexpectedTypeException('An object of class "' . get_class($input) . '" could not be converted to a plain value.', 1274799934);
702 }
703 } else {
704 $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
705 }
706 return $parameter;
707 }
708
709 /**
710 * If the given callback is set the value will be passed on the the callback function.
711 * The value will be converted to a string.
712 *
713 * @param string $value The string value that should be processed. Will be passed to the callback as first parameter.
714 * @param callable $callback The data passed to call_user_func().
715 * @param array $additionalParameters Optional additional parameters passed to the callback as second argument.
716 * @return string
717 */
718 protected function getPlainStringValue($value, $callback = NULL , array $additionalParameters = array()) {
719 if (is_callable($callback)) {
720 $value = call_user_func($callback, $value, $additionalParameters);
721 }
722 return (string)$value;
723 }
724
725 }