[TASK] Use name-resolution instead of strings where possible: 3
[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, 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 \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class:
218 $propertyValue = $this->mapResultToPropertyValue(
219 $object,
220 $propertyName,
221 $this->fetchRelated($object, $propertyName, $row[$columnName])
222 );
223 break;
224 default:
225 if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
226 $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat());
227 } elseif (TypeHandlingUtility::isCoreType($propertyData['type'])) {
228 $propertyValue = $this->mapCoreType($propertyData['type'], $row[$columnName]);
229 } else {
230 $propertyValue = $this->mapObjectToClassProperty(
231 $object,
232 $propertyName,
233 $row[$columnName]
234 );
235 }
236
237 }
238 }
239 if ($propertyValue !== NULL) {
240 $object->_setProperty($propertyName, $propertyValue);
241 }
242 }
243 }
244
245 /**
246 * Map value to a core type
247 *
248 * @param string $type
249 * @param mixed $value
250 * @return \TYPO3\CMS\Core\Type\TypeInterface
251 */
252 protected function mapCoreType($type, $value) {
253 return new $type($value);
254 }
255
256 /**
257 * Creates a DateTime from an unix timestamp or date/datetime value.
258 * If the input is empty, NULL is returned.
259 *
260 * @param int|string $value Unix timestamp or date/datetime value
261 * @param NULL|string $storageFormat Storage format for native date/datetime fields
262 * @return \DateTime
263 */
264 protected function mapDateTime($value, $storageFormat = NULL) {
265 if (empty($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
266 // 0 -> NULL !!!
267 return NULL;
268 } elseif ($storageFormat === 'date' || $storageFormat === 'datetime') {
269 // native date/datetime values are stored in UTC
270 $utcTimeZone = new \DateTimeZone('UTC');
271 $utcDateTime = new \DateTime($value, $utcTimeZone);
272 $currentTimeZone = new \DateTimeZone(date_default_timezone_get());
273 return $utcDateTime->setTimezone($currentTimeZone);
274 } else {
275 return new \DateTime(date('c', $value));
276 }
277 }
278
279 /**
280 * Fetches a collection of objects related to a property of a parent object
281 *
282 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
283 * @param string $propertyName The name of the proxied property in it's parent
284 * @param mixed $fieldValue The raw field value.
285 * @param bool $enableLazyLoading A flag indication if the related objects should be lazy loaded
286 * @return \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage|Persistence\QueryResultInterface The result
287 */
288 public function fetchRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = TRUE) {
289 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
290 if ($enableLazyLoading === TRUE && $propertyMetaData['lazy']) {
291 if ($propertyMetaData['type'] === \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class) {
292 $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage::class, $parentObject, $propertyName, $fieldValue);
293 } else {
294 if (empty($fieldValue)) {
295 $result = NULL;
296 } else {
297 $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class, $parentObject, $propertyName, $fieldValue);
298 }
299 }
300 } else {
301 $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue);
302 }
303 return $result;
304 }
305
306 /**
307 * Fetches the related objects from the storage backend.
308 *
309 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
310 * @param string $propertyName The name of the proxied property in it's parent
311 * @param mixed $fieldValue The raw field value.
312 * @return mixed
313 */
314 protected function fetchRelatedEager(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
315 return $fieldValue === '' ? $this->getEmptyRelationValue($parentObject, $propertyName) : $this->getNonEmptyRelationValue($parentObject, $propertyName, $fieldValue);
316 }
317
318 /**
319 * @param DomainObjectInterface $parentObject
320 * @param string $propertyName
321 * @return array|NULL
322 */
323 protected function getEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName) {
324 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
325 $relatesToOne = $columnMap->getTypeOfRelation() == ColumnMap::RELATION_HAS_ONE;
326 return $relatesToOne ? NULL : array();
327 }
328
329 /**
330 * @param DomainObjectInterface $parentObject
331 * @param string $propertyName
332 * @param string $fieldValue
333 * @return Persistence\QueryResultInterface
334 */
335 protected function getNonEmptyRelationValue(DomainObjectInterface $parentObject, $propertyName, $fieldValue) {
336 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
337 return $query->execute();
338 }
339
340 /**
341 * Builds and returns the prepared query, ready to be executed.
342 *
343 * @param DomainObjectInterface $parentObject
344 * @param string $propertyName
345 * @param string $fieldValue
346 * @return Persistence\QueryInterface
347 */
348 protected function getPreparedQuery(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
349 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
350 $type = $this->getType(get_class($parentObject), $propertyName);
351 $query = $this->queryFactory->create($type);
352 $query->getQuerySettings()->setRespectStoragePage(FALSE);
353 $query->getQuerySettings()->setRespectSysLanguage(FALSE);
354 if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
355 if ($columnMap->getChildSortByFieldName() !== NULL) {
356 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING));
357 }
358 } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
359 $query->setSource($this->getSource($parentObject, $propertyName));
360 if ($columnMap->getChildSortByFieldName() !== NULL) {
361 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING));
362 }
363 }
364 $query->matching($this->getConstraint($query, $parentObject, $propertyName, $fieldValue, $columnMap->getRelationTableMatchFields()));
365 return $query;
366 }
367
368 /**
369 * Builds and returns the constraint for multi value properties.
370 *
371 * @param Persistence\QueryInterface $query
372 * @param DomainObjectInterface $parentObject
373 * @param string $propertyName
374 * @param string $fieldValue
375 * @param array $relationTableMatchFields
376 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
377 */
378 protected function getConstraint(Persistence\QueryInterface $query, DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = array()) {
379 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
380 if ($columnMap->getParentKeyFieldName() !== NULL) {
381 $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject);
382 if ($columnMap->getParentTableFieldName() !== NULL) {
383 $constraint = $query->logicalAnd(
384 $constraint,
385 $query->equals($columnMap->getParentTableFieldName(), $this->getDataMap(get_class($parentObject))->getTableName())
386 );
387 }
388 } else {
389 $constraint = $query->in('uid', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $fieldValue));
390 }
391 if (count($relationTableMatchFields) > 0) {
392 foreach ($relationTableMatchFields as $relationTableMatchFieldName => $relationTableMatchFieldValue) {
393 $constraint = $query->logicalAnd($constraint, $query->equals($relationTableMatchFieldName, $relationTableMatchFieldValue));
394 }
395 }
396 return $constraint;
397 }
398
399 /**
400 * Builds and returns the source to build a join for a m:n relation.
401 *
402 * @param DomainObjectInterface $parentObject
403 * @param string $propertyName
404 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
405 */
406 protected function getSource(DomainObjectInterface $parentObject, $propertyName) {
407 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
408 $left = $this->qomFactory->selector(NULL, $columnMap->getRelationTableName());
409 $childClassName = $this->getType(get_class($parentObject), $propertyName);
410 $right = $this->qomFactory->selector($childClassName, $columnMap->getChildTableName());
411 $joinCondition = $this->qomFactory->equiJoinCondition($columnMap->getRelationTableName(), $columnMap->getChildKeyFieldName(), $columnMap->getChildTableName(), 'uid');
412 $source = $this->qomFactory->join($left, $right, Persistence\Generic\Query::JCR_JOIN_TYPE_INNER, $joinCondition);
413 return $source;
414 }
415
416 /**
417 * Returns the mapped classProperty from the identiyMap or
418 * mapResultToPropertyValue()
419 *
420 * If the field value is empty and the column map has no parent key field name,
421 * the relation will be empty. If the identityMap has a registered object of
422 * the correct type and identity (fieldValue), this function returns that object.
423 * Otherwise, it proceeds with mapResultToPropertyValue().
424 *
425 * @param DomainObjectInterface $parentObject
426 * @param string $propertyName
427 * @param mixed $fieldValue the raw field value
428 * @return mixed
429 * @see mapResultToPropertyValue()
430 */
431 protected function mapObjectToClassProperty(DomainObjectInterface $parentObject, $propertyName, $fieldValue) {
432 if ($this->propertyMapsByForeignKey($parentObject, $propertyName)) {
433 $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
434 $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
435 } else {
436 if ($fieldValue === '') {
437 $propertyValue = $this->getEmptyRelationValue($parentObject, $propertyName);
438 } else {
439 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
440 if ($this->persistenceSession->hasIdentifier($fieldValue, $propertyMetaData['type'])) {
441 $propertyValue = $this->persistenceSession->getObjectByIdentifier($fieldValue, $propertyMetaData['type']);
442 } else {
443 $result = $this->fetchRelated($parentObject, $propertyName, $fieldValue);
444 $propertyValue = $this->mapResultToPropertyValue($parentObject, $propertyName, $result);
445 }
446 }
447 }
448
449 return $propertyValue;
450 }
451
452 /**
453 * Checks if the relation is based on a foreign key.
454 *
455 * @param DomainObjectInterface $parentObject
456 * @param string $propertyName
457 * @return bool TRUE if the property is mapped
458 */
459 protected function propertyMapsByForeignKey(DomainObjectInterface $parentObject, $propertyName) {
460 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
461 return ($columnMap->getParentKeyFieldName() !== NULL);
462 }
463
464 /**
465 * Returns the given result as property value of the specified property type.
466 *
467 * @param DomainObjectInterface $parentObject
468 * @param string $propertyName
469 * @param mixed $result The result
470 * @return mixed
471 */
472 public function mapResultToPropertyValue(DomainObjectInterface $parentObject, $propertyName, $result) {
473 $propertyValue = NULL;
474 if ($result instanceof Persistence\Generic\LoadingStrategyInterface) {
475 $propertyValue = $result;
476 } else {
477 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
478 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'SplObjectStorage', \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class), TRUE)) {
479 $objects = array();
480 foreach ($result as $value) {
481 $objects[] = $value;
482 }
483 if ($propertyMetaData['type'] === 'ArrayObject') {
484 $propertyValue = new \ArrayObject($objects);
485 } elseif (in_array($propertyMetaData['type'], array(\TYPO3\CMS\Extbase\Persistence\ObjectStorage::class), TRUE)) {
486 $propertyValue = new Persistence\ObjectStorage();
487 foreach ($objects as $object) {
488 $propertyValue->attach($object);
489 }
490 $propertyValue->_memorizeCleanState();
491 } else {
492 $propertyValue = $objects;
493 }
494 } elseif (strpbrk($propertyMetaData['type'], '_\\') !== FALSE) {
495 if (is_object($result) && $result instanceof Persistence\QueryResultInterface) {
496 $propertyValue = $result->getFirst();
497 } else {
498 $propertyValue = $result;
499 }
500 }
501 }
502 return $propertyValue;
503 }
504
505 /**
506 * Counts the number of related objects assigned to a property of a parent object
507 *
508 * @param DomainObjectInterface $parentObject The object instance this proxy is part of
509 * @param string $propertyName The name of the proxied property in it's parent
510 * @param mixed $fieldValue The raw field value.
511 * @return int
512 */
513 public function countRelated(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
514 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
515 return $query->execute()->count();
516 }
517
518 /**
519 * Delegates the call to the Data Map.
520 * Returns TRUE if the property is persistable (configured in $TCA)
521 *
522 * @param string $className The property name
523 * @param string $propertyName The property name
524 * @return bool TRUE if the property is persistable (configured in $TCA)
525 */
526 public function isPersistableProperty($className, $propertyName) {
527 $dataMap = $this->getDataMap($className);
528 return $dataMap->isPersistableProperty($propertyName);
529 }
530
531 /**
532 * Returns a data map for a given class name
533 *
534 * @param string $className The class name you want to fetch the Data Map for
535 * @throws Persistence\Generic\Exception
536 * @return DataMap The data map
537 */
538 public function getDataMap($className) {
539 if (!is_string($className) || strlen($className) === 0) {
540 throw new Persistence\Generic\Exception('No class name was given to retrieve the Data Map for.', 1251315965);
541 }
542 if (!isset($this->dataMaps[$className])) {
543 $this->dataMaps[$className] = $this->dataMapFactory->buildDataMap($className);
544 }
545 return $this->dataMaps[$className];
546 }
547
548 /**
549 * Returns the selector (table) name for a given class name.
550 *
551 * @param string $className
552 * @return string The selector name
553 */
554 public function convertClassNameToTableName($className = NULL) {
555 if ($className !== NULL) {
556 $tableName = $this->getDataMap($className)->getTableName();
557 } else {
558 $tableName = strtolower($className);
559 }
560 return $tableName;
561 }
562
563 /**
564 * Returns the column name for a given property name of the specified class.
565 *
566 * @param string $propertyName
567 * @param string $className
568 * @return string The column name
569 */
570 public function convertPropertyNameToColumnName($propertyName, $className = NULL) {
571 if (!empty($className)) {
572 $dataMap = $this->getDataMap($className);
573 if ($dataMap !== NULL) {
574 $columnMap = $dataMap->getColumnMap($propertyName);
575 if ($columnMap !== NULL) {
576 return $columnMap->getColumnName();
577 }
578 }
579 }
580 return \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($propertyName);
581 }
582
583 /**
584 * Returns the type of a child object.
585 *
586 * @param string $parentClassName The class name of the object this proxy is part of
587 * @param string $propertyName The name of the proxied property in it's parent
588 * @throws UnexpectedTypeException
589 * @return string The class name of the child object
590 */
591 public function getType($parentClassName, $propertyName) {
592 $propertyMetaData = $this->reflectionService->getClassSchema($parentClassName)->getProperty($propertyName);
593 if (!empty($propertyMetaData['elementType'])) {
594 $type = $propertyMetaData['elementType'];
595 } elseif (!empty($propertyMetaData['type'])) {
596 $type = $propertyMetaData['type'];
597 } else {
598 throw new UnexpectedTypeException('Could not determine the child object type.', 1251315967);
599 }
600 return $type;
601 }
602
603 /**
604 * Returns a plain value, i.e. objects are flattened out if possible.
605 * Multi value objects or arrays will be converted to a comma-separated list for use in IN SQL queries.
606 *
607 * @param mixed $input The value that will be converted.
608 * @param ColumnMap $columnMap Optional column map for retrieving the date storage format.
609 * @param callable $parseStringValueCallback Optional callback method that will be called for string values. Can be used to do database quotation.
610 * @param array $parseStringValueCallbackParameters Additional parameters that will be passed to the callabck as second parameter.
611 * @throws \InvalidArgumentException
612 * @throws UnexpectedTypeException
613 * @return int|string
614 */
615 public function getPlainValue($input, $columnMap = NULL, $parseStringValueCallback = NULL, array $parseStringValueCallbackParameters = array()) {
616 if ($input === NULL) {
617 return 'NULL';
618 }
619 if ($input instanceof Persistence\Generic\LazyLoadingProxy) {
620 $input = $input->_loadRealInstance();
621 }
622
623 if (is_bool($input)) {
624 $parameter = (int)$input;
625 } elseif ($input instanceof \DateTime) {
626 if (!is_null($columnMap) && !is_null($columnMap->getDateTimeStorageFormat())) {
627 $storageFormat = $columnMap->getDateTimeStorageFormat();
628 switch ($storageFormat) {
629 case 'datetime':
630 $parameter = $input->format('Y-m-d H:i:s');
631 break;
632 case 'date':
633 $parameter = $input->format('Y-m-d');
634 break;
635 default:
636 throw new \InvalidArgumentException('Column map DateTime format "' . $storageFormat . '" is unknown. Allowed values are datetime or date.', 1395353470);
637 }
638 } else {
639 $parameter = $input->format('U');
640 }
641 } elseif (TypeHandlingUtility::isValidTypeForMultiValueComparison($input)) {
642 $plainValueArray = array();
643 foreach ($input as $inputElement) {
644 $plainValueArray[] = $this->getPlainValue($inputElement, $columnMap, $parseStringValueCallback, $parseStringValueCallbackParameters);
645 }
646 $parameter = implode(',', $plainValueArray);
647 } elseif ($input instanceof DomainObjectInterface) {
648 $parameter = (int)$input->getUid();
649 } elseif (is_object($input)) {
650 if (TypeHandlingUtility::isCoreType($input)) {
651 $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
652 } else {
653 throw new UnexpectedTypeException('An object of class "' . get_class($input) . '" could not be converted to a plain value.', 1274799934);
654 }
655 } else {
656 $parameter = $this->getPlainStringValue($input, $parseStringValueCallback, $parseStringValueCallbackParameters);
657 }
658 return $parameter;
659 }
660
661 /**
662 * If the given callback is set the value will be passed on the the callback function.
663 * The value will be converted to a string.
664 *
665 * @param string $value The string value that should be processed. Will be passed to the callback as first parameter.
666 * @param callable $callback The data passed to call_user_func().
667 * @param array $additionalParameters Optional additional parameters passed to the callback as second argument.
668 * @return string
669 */
670 protected function getPlainStringValue($value, $callback = NULL , array $additionalParameters = array()) {
671 if (is_callable($callback)) {
672 $value = call_user_func($callback, $value, $additionalParameters);
673 }
674 return (string)$value;
675 }
676 }