[TASK] Update php-cs-fixer to 2.5.0
[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\DomainObject\DomainObjectInterface;
18 use TYPO3\CMS\Extbase\Object\Exception\CannotReconstituteObjectException;
19 use TYPO3\CMS\Extbase\Persistence;
20 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
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 = [];
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 {
82 $this->reflectionService = $reflectionService;
83 }
84
85 /**
86 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
87 */
88 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
89 {
90 $this->qomFactory = $qomFactory;
91 }
92
93 /**
94 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession
95 */
96 public function injectPersistenceSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $persistenceSession)
97 {
98 $this->persistenceSession = $persistenceSession;
99 }
100
101 /**
102 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory
103 */
104 public function injectDataMapFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory)
105 {
106 $this->dataMapFactory = $dataMapFactory;
107 }
108
109 /**
110 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory
111 */
112 public function injectQueryFactory(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface $queryFactory)
113 {
114 $this->queryFactory = $queryFactory;
115 }
116
117 /**
118 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
119 */
120 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
121 {
122 $this->objectManager = $objectManager;
123 }
124
125 /**
126 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
127 */
128 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
129 {
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 {
142 $objects = [];
143 foreach ($rows as $row) {
144 $objects[] = $this->mapSingleRow($this->getTargetType($className, $row), $row);
145 }
146 return $objects;
147 }
148
149 /**
150 * Returns the target type for the given row.
151 *
152 * @param string $className The name of the class
153 * @param array $row A single array with field_name => value pairs
154 * @return string The target type (a class name)
155 */
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
172 /**
173 * Maps a single row on an object of the given class
174 *
175 * @param string $className The name of the target class
176 * @param array $row A single array with field_name => value pairs
177 * @return object An object of the given class
178 */
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
194 /**
195 * Emits a signal after mapping a single row.
196 *
197 * @param DomainObjectInterface $object The mapped object
198 */
199 protected function emitAfterMappingSingleRow(DomainObjectInterface $object)
200 {
201 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterMappingSingleRow', [$object]);
202 }
203
204 /**
205 * Creates a skeleton of the specified object
206 *
207 * @param string $className Name of the class to create a skeleton for
208 * @throws CannotReconstituteObjectException
209 * @return object The object skeleton
210 */
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
223 /**
224 * Sets the given properties on the object.
225 *
226 * @param DomainObjectInterface $object The object to set properties on
227 * @param array $row
228 */
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
302 /**
303 * Map value to a core type
304 *
305 * @param string $type
306 * @param mixed $value
307 * @return \TYPO3\CMS\Core\Type\TypeInterface
308 */
309 protected function mapCoreType($type, $value)
310 {
311 return new $type($value);
312 }
313
314 /**
315 * Creates a DateTime from an unix timestamp or date/datetime value.
316 * If the input is empty, NULL is returned.
317 *
318 * @param int|string $value Unix timestamp or date/datetime value
319 * @param NULL|string $storageFormat Storage format for native date/datetime fields
320 * @param NULL|string $targetType The object class name to be created
321 * @return \DateTime
322 */
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', $value));
338 }
339
340 /**
341 * Fetches a collection of objects related to a property of a parent object
342 *
343 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
344 * @param string $propertyName The name of the proxied property in it's parent
345 * @param mixed $fieldValue The raw field value.
346 * @param bool $enableLazyLoading A flag indication if the related objects should be lazy loaded
347 * @return \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage|Persistence\QueryResultInterface The result
348 */
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
368 /**
369 * Fetches the related objects from the storage backend.
370 *
371 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
372 * @param string $propertyName The name of the proxied property in it's parent
373 * @param mixed $fieldValue The raw field value.
374 * @return mixed
375 */
376 protected function fetchRelatedEager(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
377 {
378 return $fieldValue === '' ? $this->getEmptyRelationValue($parentObject, $propertyName) : $this->getNonEmptyRelationValue($parentObject, $propertyName, $fieldValue);
379 }
380
381 /**
382 * @param DomainObjectInterface $parentObject
383 * @param string $propertyName
384 * @return array|NULL
385 */
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
393 /**
394 * @param DomainObjectInterface $parentObject
395 * @param string $propertyName
396 * @param string $fieldValue
397 * @return Persistence\QueryResultInterface
398 */
399 protected function getNonEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName, $fieldValue)
400 {
401 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
402 return $query->execute();
403 }
404
405 /**
406 * Builds and returns the prepared query, ready to be executed.
407 *
408 * @param DomainObjectInterface $parentObject
409 * @param string $propertyName
410 * @param string $fieldValue
411 * @return Persistence\QueryInterface
412 */
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
434 /**
435 * Builds and returns the constraint for multi value properties.
436 *
437 * @param Persistence\QueryInterface $query
438 * @param DomainObjectInterface $parentObject
439 * @param string $propertyName
440 * @param string $fieldValue
441 * @param array $relationTableMatchFields
442 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
443 */
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
466 /**
467 * Builds and returns the source to build a join for a m:n relation.
468 *
469 * @param DomainObjectInterface $parentObject
470 * @param string $propertyName
471 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
472 */
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
484 /**
485 * Returns the mapped classProperty from the identiyMap or
486 * mapResultToPropertyValue()
487 *
488 * If the field value is empty and the column map has no parent key field name,
489 * the relation will be empty. If the persistence session has a registered object of
490 * the correct type and identity (fieldValue), this function returns that object.
491 * Otherwise, it proceeds with mapResultToPropertyValue().
492 *
493 * @param DomainObjectInterface $parentObject
494 * @param string $propertyName
495 * @param mixed $fieldValue the raw field value
496 * @return mixed
497 * @see mapResultToPropertyValue()
498 */
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
521 /**
522 * Checks if the relation is based on a foreign key.
523 *
524 * @param DomainObjectInterface $parentObject
525 * @param string $propertyName
526 * @return bool TRUE if the property is mapped
527 */
528 protected function propertyMapsByForeignKey(DomainObjectInterface $parentObject, $propertyName)
529 {
530 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
531 return $columnMap->getParentKeyFieldName() !== null;
532 }
533
534 /**
535 * Returns the given result as property value of the specified property type.
536 *
537 * @param DomainObjectInterface $parentObject
538 * @param string $propertyName
539 * @param mixed $result The result
540 * @return mixed
541 */
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
576 /**
577 * Counts the number of related objects assigned to a property of a parent object
578 *
579 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
580 * @param string $propertyName The name of the proxied property in it's parent
581 * @param mixed $fieldValue The raw field value.
582 * @return int
583 */
584 public function countRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
585 {
586 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
587 return $query->execute()->count();
588 }
589
590 /**
591 * Delegates the call to the Data Map.
592 * Returns TRUE if the property is persistable (configured in $TCA)
593 *
594 * @param string $className The property name
595 * @param string $propertyName The property name
596 * @return bool TRUE if the property is persistable (configured in $TCA)
597 */
598 public function isPersistableProperty($className, $propertyName)
599 {
600 $dataMap = $this->getDataMap($className);
601 return $dataMap->isPersistableProperty($propertyName);
602 }
603
604 /**
605 * Returns a data map for a given class name
606 *
607 * @param string $className The class name you want to fetch the Data Map for
608 * @throws Persistence\Generic\Exception
609 * @return DataMap The data map
610 */
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
622 /**
623 * Returns the selector (table) name for a given class name.
624 *
625 * @param string $className
626 * @return string The selector name
627 */
628 public function convertClassNameToTableName($className)
629 {
630 return $this->getDataMap($className)->getTableName();
631 }
632
633 /**
634 * Returns the column name for a given property name of the specified class.
635 *
636 * @param string $propertyName
637 * @param string $className
638 * @return string The column name
639 */
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
654 /**
655 * Returns the type of a child object.
656 *
657 * @param string $parentClassName The class name of the object this proxy is part of
658 * @param string $propertyName The name of the proxied property in it's parent
659 * @throws UnexpectedTypeException
660 * @return string The class name of the child object
661 */
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
675 /**
676 * Returns a plain value, i.e. objects are flattened out if possible.
677 * Multi value objects or arrays will be converted to a comma-separated list for use in IN SQL queries.
678 *
679 * @param mixed $input The value that will be converted.
680 * @param ColumnMap $columnMap Optional column map for retrieving the date storage format.
681 * @param callable $parseStringValueCallback Optional callback method that will be called for string values. Can be used to do database quotation.
682 * @param array $parseStringValueCallbackParameters Additional parameters that will be passed to the callabck as second parameter.
683 * @throws \InvalidArgumentException
684 * @throws UnexpectedTypeException
685 * @return int|string
686 */
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();
721 } elseif (TypeHandlingUtility::isValidTypeForMultiValueComparison($input)) {
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
739 /**
740 * If the given callback is set the value will be passed on the the callback function.
741 * The value will be converted to a string.
742 *
743 * @param string $value The string value that should be processed. Will be passed to the callback as first parameter.
744 * @param callable $callback The data passed to call_user_func().
745 * @param array $additionalParameters Optional additional parameters passed to the callback as second argument.
746 * @return string
747 */
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 }