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