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