[!!!][~TASK] Extbase (Utility): Moved configureDispatcher() and registerPlugin()...
[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 * @var Tx_Extbase_Reflection_Service
67 */
68 protected $reflectionService;
69
70 /**
71 * @var Tx_Extbase_Persistence_ManagerInterface
72 */
73 protected $persistenceManager;
74
75 /**
76 * Constructs the Property Mapper.
77 */
78 public function __construct() {
79 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager');
80 $this->validatorResolver = t3lib_div::makeInstance('Tx_Extbase_Validation_ValidatorResolver');
81 $this->validatorResolver->injectObjectManager($objectManager);
82 $this->persistenceManager = Tx_Extbase_Dispatcher::getPersistenceManager();
83 }
84
85 /**
86 * Injects the Reflection Service
87 *
88 * @param Tx_Extbase_Reflection_Service
89 * @return void
90 */
91 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
92 $this->reflectionService = $reflectionService;
93 }
94
95 /**
96 * Maps the given properties to the target object and validates the properties according to the defined
97 * validators. If the result object is not valid, the operation will be undone (the target object remains
98 * unchanged) and this method returns FALSE.
99 *
100 * If in doubt, always prefer this method over the map() method because skipping validation can easily become
101 * a security issue.
102 *
103 * @param array $propertyNames Names of the properties to map.
104 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object.
105 * @param object $target The target object
106 * @param Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator A validator used for validating the target object
107 * @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.
108 * @return boolean TRUE if the mapped properties are valid, otherwise FALSE
109 * @see getMappingResults()
110 * @see map()
111 * @api
112 */
113 public function mapAndValidate(array $propertyNames, $source, $target, $optionalPropertyNames = array(), Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator) {
114 $backupProperties = array();
115
116 $this->map($propertyNames, $source, $backupProperties, $optionalPropertyNames);
117 if ($this->mappingResults->hasErrors()) return FALSE;
118
119 $this->map($propertyNames, $source, $target, $optionalPropertyNames);
120 if ($this->mappingResults->hasErrors()) return FALSE;
121
122 if ($targetObjectValidator->isValid($target) !== TRUE) {
123 $this->addErrorsFromObjectValidator($targetObjectValidator->getErrors());
124 $backupMappingResult = $this->mappingResults;
125 $this->map($propertyNames, $backupProperties, $source, $optionalPropertyNames);
126 $this->mappingResults = $backupMappingResult;
127 }
128 return (!$this->mappingResults->hasErrors());
129 }
130
131 /**
132 * Add errors to the mapping result from an object validator (property errors).
133 *
134 * @param array Array of Tx_Extbase_Validation_PropertyError
135 * @return void
136 */
137 protected function addErrorsFromObjectValidator($errors) {
138 foreach ($errors as $error) {
139 if ($error instanceof Tx_Extbase_Validation_PropertyError) {
140 $propertyName = $error->getPropertyName();
141 $this->mappingResults->addError($error, $propertyName);
142 }
143 }
144 }
145
146 /**
147 * Maps the given properties to the target object WITHOUT VALIDATING THE RESULT.
148 * If the properties could be set, this method returns TRUE, otherwise FALSE.
149 * Returning TRUE does not mean that the target object is valid and secure!
150 *
151 * Only use this method if you're sure that you don't need validation!
152 *
153 * @param array $propertyNames Names of the properties to map.
154 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object.
155 * @param object $target The target object
156 * @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.
157 * @return boolean TRUE if the properties could be mapped, otherwise FALSE
158 * @see mapAndValidate()
159 * @api
160 */
161 public function map(array $propertyNames, $source, $target, $optionalPropertyNames = array()) {
162 if (!is_object($source) && !is_array($source)) throw new Tx_Extbase_Property_Exception_InvalidSource('The source object must be a valid object or array, ' . gettype($target) . ' given.', 1187807099);
163 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);
164
165 if (is_object($target)) {
166 $targetClassSchema = $this->reflectionService->getClassSchema(get_class($target));
167 } else {
168 $targetClassSchema = NULL;
169 }
170
171 $this->mappingResults = new Tx_Extbase_Property_MappingResults();
172 $propertyValues = array();
173
174 foreach ($propertyNames as $propertyName) {
175 if (is_array($source) || $source instanceof ArrayAccess) {
176 if (isset($source[$propertyName])) $propertyValues[$propertyName] = $source[$propertyName];
177 } else {
178 $propertyValues[$propertyName] = Tx_Extbase_Reflection_ObjectAccess::getProperty($source, $propertyName);
179 }
180 }
181 foreach ($propertyNames as $propertyName) {
182 if (isset($propertyValues[$propertyName])) {
183
184 // source is array with (seemingly) uid strings and we have a target knowing the property
185 if (is_array($propertyValues[$propertyName])
186 && is_string(current($propertyValues[$propertyName]))
187 && $targetClassSchema !== NULL
188 && $targetClassSchema->hasProperty($propertyName)) {
189
190 $propertyMetaData = $targetClassSchema->getProperty($propertyName);
191 // the target is array-like and the elementType is a class
192 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'SplObjectStorage')) && strpos($propertyMetaData['elementType'], '_') !== FALSE) {
193 foreach ($propertyValues[$propertyName] as $key => $value) {
194 // convert to object and override original value
195 $existingObject = $this->persistenceManager->getBackend()->getObjectByIdentifier($value, get_class($target));
196 if ($existingObject === FALSE) throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('Querying the repository for the specified object was not successful.', 1249379517);
197 $propertyValues[$propertyName][$key] = $existingObject;
198 }
199 }
200 // make sure we hand out what is expected
201 if ($propertyMetaData['type'] === 'ArrayObject') {
202 $propertyValues[$propertyName] = new ArrayObject($propertyValues[$propertyName]);
203 } elseif ($propertyMetaData['type']=== 'SplObjectStorage') {
204 $objects = $propertyValues[$propertyName];
205 $propertyValues[$propertyName] = new Tx_Extbase_Persistence_ObjectStorage();
206 foreach ($objects as $object) {
207 $propertyValues[$propertyName]->attach($object);
208 }
209 }
210 }
211
212 if (is_array($target)) {
213 $target[$propertyName] = $source[$propertyName];
214 } elseif (Tx_Extbase_Reflection_ObjectAccess::setProperty($target, $propertyName, $propertyValues[$propertyName]) === FALSE) {
215 $this->mappingResults->addError(new Tx_Extbase_Error_Error("Property '$propertyName' could not be set." , 1236783102), $propertyName);
216 }
217 } elseif (!in_array($propertyName, $optionalPropertyNames)) {
218 $this->mappingResults->addError(new Tx_Extbase_Error_Error("Required property '$propertyName' does not exist." , 1236785359), $propertyName);
219 }
220 }
221 return (!$this->mappingResults->hasErrors() && !$this->mappingResults->hasWarnings());
222 }
223
224 /**
225 * Returns the results of the last mapping operation.
226 *
227 * @return Tx_Extbase_Property_MappingResults The mapping results (or NULL if no mapping has been carried out yet)
228 * @api
229 */
230 public function getMappingResults() {
231 return $this->mappingResults;
232 }
233 }
234
235 ?>