2 /***************************************************************
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
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.
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
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
33 * During the mapping process, the property values are validated and the result of this
34 * validation can be queried.
36 * The following code would map the property of the source array to the target:
38 * $target = new ArrayObject();
39 * $source = new ArrayObject(
41 * 'someProperty' => 'SomeValue'
44 * $mapper->mapAndValidate(array('someProperty'), $source, $target);
46 * Now the target object equals the source object.
49 * @subpackage Property
52 class Tx_Extbase_Property_Mapper
{
55 * Results of the last mapping operation
56 * @var Tx_Extbase_Property_MappingResults
58 protected $mappingResults;
61 * @var Tx_Extbase_Validation_ValidatorResolver
63 protected $validatorResolver;
66 * @var Tx_Extbase_Reflection_Service
68 protected $reflectionService;
71 * @var Tx_Extbase_Persistence_ManagerInterface
73 protected $persistenceManager;
76 * Constructs the Property Mapper.
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();
86 * Injects the Reflection Service
88 * @param Tx_Extbase_Reflection_Service
91 public function injectReflectionService(Tx_Extbase_Reflection_Service
$reflectionService) {
92 $this->reflectionService
= $reflectionService;
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.
100 * If in doubt, always prefer this method over the map() method because skipping validation can easily become
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()
113 public function mapAndValidate(array $propertyNames, $source, $target, $optionalPropertyNames = array(), Tx_Extbase_Validation_Validator_ObjectValidatorInterface
$targetObjectValidator) {
114 $backupProperties = array();
116 $this->map($propertyNames, $source, $backupProperties, $optionalPropertyNames);
117 if ($this->mappingResults
->hasErrors()) return FALSE;
119 $this->map($propertyNames, $source, $target, $optionalPropertyNames);
120 if ($this->mappingResults
->hasErrors()) return FALSE;
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;
128 return (!$this->mappingResults
->hasErrors());
132 * Add errors to the mapping result from an object validator (property errors).
134 * @param array Array of Tx_Extbase_Validation_PropertyError
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);
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!
151 * Only use this method if you're sure that you don't need validation!
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()
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);
165 if (is_object($target)) {
166 $targetClassSchema = $this->reflectionService
->getClassSchema(get_class($target));
168 $targetClassSchema = NULL;
171 $this->mappingResults
= new Tx_Extbase_Property_MappingResults();
172 $propertyValues = array();
174 foreach ($propertyNames as $propertyName) {
175 if (is_array($source) ||
$source instanceof ArrayAccess
) {
176 if (isset($source[$propertyName])) $propertyValues[$propertyName] = $source[$propertyName];
178 $propertyValues[$propertyName] = Tx_Extbase_Reflection_ObjectAccess
::getProperty($source, $propertyName);
181 foreach ($propertyNames as $propertyName) {
182 if (isset($propertyValues[$propertyName])) {
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)) {
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;
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);
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);
217 } elseif (!in_array($propertyName, $optionalPropertyNames)) {
218 $this->mappingResults
->addError(new Tx_Extbase_Error_Error("Required property '$propertyName' does not exist." , 1236785359), $propertyName);
221 return (!$this->mappingResults
->hasErrors() && !$this->mappingResults
->hasWarnings());
225 * Returns the results of the last mapping operation.
227 * @return Tx_Extbase_Property_MappingResults The mapping results (or NULL if no mapping has been carried out yet)
230 public function getMappingResults() {
231 return $this->mappingResults
;