a1f1949685af03f90cad74cf173737b68630d8bf
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Property / Mapper.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * The Property Mapper maps properties from a source onto a given target object, often a
30 * (domain-) model. Which properties are required and how they should be filtered can
31 * be customized.
32 *
33 * During the mapping process, the property values are validated and the result of this
34 * validation can be queried.
35 *
36 * The following code would map the property of the source array to the target:
37 *
38 * $target = new ArrayObject();
39 * $source = new ArrayObject(
40 * array(
41 * 'someProperty' => 'SomeValue'
42 * )
43 * );
44 * $mapper->mapAndValidate(array('someProperty'), $source, $target);
45 *
46 * Now the target object equals the source object.
47 *
48 * @package Extbase
49 * @subpackage Property
50 * @version $Id$
51 */
52 class Tx_Extbase_Property_Mapper {
53
54 /**
55 * Results of the last mapping operation
56 * @var Tx_Extbase_Property_MappingResults
57 */
58 protected $mappingResults;
59
60 /**
61 * @var Tx_Extbase_Validation_ValidatorResolver
62 */
63 protected $validatorResolver;
64
65 /**
66 * Constructs the Property Mapper.
67 */
68 public function __construct() {
69 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager');
70 $this->validatorResolver = t3lib_div::makeInstance('Tx_Extbase_Validation_ValidatorResolver');
71 $this->validatorResolver->injectObjectManager($objectManager);
72 }
73
74 /**
75 * Maps the given properties to the target object and validates the properties according to the defined
76 * validators. If the result object is not valid, the operation will be undone (the target object remains
77 * unchanged) and this method returns FALSE.
78 *
79 * If in doubt, always prefer this method over the map() method because skipping validation can easily become
80 * a security issue.
81 *
82 * @param array $propertyNames Names of the properties to map.
83 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object.
84 * @param object $target The target object
85 * @param Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator A validator used for validating the target object
86 * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued.
87 * @return boolean TRUE if the mapped properties are valid, otherwise FALSE
88 * @see getMappingResults()
89 * @see map()
90 */
91 public function mapAndValidate(array $propertyNames, $source, $target, $optionalPropertyNames = array(), Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator) {
92 $backupProperties = array();
93
94 $this->map($propertyNames, $source, $backupProperties, $optionalPropertyNames);
95 if ($this->mappingResults->hasErrors()) return FALSE;
96
97 $this->map($propertyNames, $source, $target, $optionalPropertyNames);
98 if ($this->mappingResults->hasErrors()) return FALSE;
99
100 if ($targetObjectValidator->isValid($target) !== TRUE) {
101 $this->addErrorsFromObjectValidator($targetObjectValidator->getErrors());
102 $backupMappingResult = $this->mappingResults;
103 $this->map($propertyNames, $backupProperties, $source, $optionalPropertyNames);
104 $this->mappingResults = $backupMappingResult;
105 }
106 return (!$this->mappingResults->hasErrors());
107 }
108
109 /**
110 * Add errors to the mapping result from an object validator (property errors).
111 *
112 * @param array Array of Tx_Extbase_Validation_PropertyError
113 * @return void
114 */
115 protected function addErrorsFromObjectValidator($errors) {
116 foreach ($errors as $error) {
117 if ($error instanceof Tx_Extbase_Validation_PropertyError) {
118 $propertyName = $error->getPropertyName();
119 $this->mappingResults->addError($error, $propertyName);
120 }
121 }
122 }
123
124 /**
125 * Maps the given properties to the target object WITHOUT VALIDATING THE RESULT.
126 * If the properties could be set, this method returns TRUE, otherwise FALSE.
127 * Returning TRUE does not mean that the target object is valid and secure!
128 *
129 * Only use this method if you're sure that you don't need validation!
130 *
131 * @param array $propertyNames Names of the properties to map.
132 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object.
133 * @param object $target The target object
134 * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued.
135 * @return boolean TRUE if the properties could be mapped, otherwise FALSE
136 * @see mapAndValidate()
137 */
138 public function map(array $propertyNames, $source, $target, $optionalPropertyNames = array()) {
139 if (!is_object($source) && !is_array($source) && ($source instanceof ArrayAccess)) throw new Tx_Extbase_Property_Exception_InvalidSource('The source object must be a valid object or array, ' . gettype($target) . ' given.', 1187807099);
140 if (!is_object($target) && !is_array($target)) throw new Tx_Extbase_Property_Exception_InvalidTarget('The target object must be a valid object or array, ' . gettype($target) . ' given.', 1187807099);
141
142 $this->mappingResults = t3lib_div::makeInstance('Tx_Extbase_Property_MappingResults');
143 $propertyValues = array();
144
145 foreach ($propertyNames as $propertyName) {
146 if (is_array($source) || $source instanceof ArrayAccess) {
147 if (isset($source[$propertyName])) $propertyValues[$propertyName] = $source[$propertyName];
148 } else {
149 $propertyValues[$propertyName] = Tx_Extbase_Reflection_ObjectAccess::getProperty($source, $propertyName);
150 }
151 }
152 foreach ($propertyNames as $propertyName) {
153 if (isset($propertyValues[$propertyName])) {
154 if (is_array($target)) {
155 $target[$propertyName] = $source[$propertyName];
156 } elseif (Tx_Extbase_Reflection_ObjectAccess::setProperty($target, $propertyName, $propertyValues[$propertyName]) === FALSE) {
157 $this->mappingResults->addError(t3lib_div::makeInstance('Tx_Extbase_Error_Error', "Property '$propertyName' could not be set." , 1236783102), $propertyName);
158 }
159 } elseif (!in_array($propertyName, $optionalPropertyNames)) {
160 $this->mappingResults->addError(t3lib_div::makeInstance('Tx_Extbase_Error_Error', "Required property '$propertyName' does not exist." , 1236785359), $propertyName);
161 }
162 }
163
164 return (!$this->mappingResults->hasErrors() && !$this->mappingResults->hasWarnings());
165 }
166
167 /**
168 * Returns the results of the last mapping operation.
169 *
170 * @return Tx_Extbase_Property_MappingResults The mapping results (or NULL if no mapping has been carried out yet)
171 */
172 public function getMappingResults() {
173 return $this->mappingResults;
174 }
175 }
176
177 ?>