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