Created
February 25, 2013 13:04
-
-
Save pumatertion/5029688 to your computer and use it in GitHub Desktop.
Respect "effectiveTargetType" on childProperties of a Collection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace TYPO3\Flow\Property\TypeConverter; | |
/* * | |
* This script belongs to the TYPO3 Flow framework. * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU Lesser General Public License, either version 3 * | |
* of the License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use TYPO3\Flow\Annotations as Flow; | |
/** | |
* This converter transforms arrays or strings to persistent objects. It does the following: | |
* | |
* - If the input is string, it is assumed to be a UUID. Then, the object is fetched from persistence. | |
* - If the input is array, we check if it has an identity property. | |
* | |
* - If the input has an identity property and NO additional properties, we fetch the object from persistence. | |
* - If the input has an identity property AND additional properties, we fetch the object from persistence, | |
* and set the sub-properties. We only do this if the configuration option "CONFIGURATION_MODIFICATION_ALLOWED" is TRUE. | |
* - If the input has NO identity property, but additional properties, we create a new object and return it. | |
* However, we only do this if the configuration option "CONFIGURATION_CREATION_ALLOWED" is TRUE. | |
* | |
* @api | |
* @Flow\Scope("singleton") | |
*/ | |
class PersistentObjectConverter extends ObjectConverter { | |
/** | |
* @var string | |
*/ | |
const PATTERN_MATCH_UUID = '/([a-f0-9]){8}-([a-f0-9]){4}-([a-f0-9]){4}-([a-f0-9]){4}-([a-f0-9]){12}/'; | |
/** | |
* @var integer | |
*/ | |
const CONFIGURATION_MODIFICATION_ALLOWED = 1; | |
/** | |
* @var integer | |
*/ | |
const CONFIGURATION_CREATION_ALLOWED = 2; | |
/** | |
* @var array | |
*/ | |
protected $sourceTypes = array('string', 'array'); | |
/** | |
* @var integer | |
*/ | |
protected $priority = 1; | |
/** | |
* @var \TYPO3\Flow\Persistence\PersistenceManagerInterface | |
*/ | |
protected $persistenceManager; | |
/** | |
* @param \TYPO3\Flow\Persistence\PersistenceManagerInterface $persistenceManager | |
* @return void | |
*/ | |
public function injectPersistenceManager(\TYPO3\Flow\Persistence\PersistenceManagerInterface $persistenceManager) { | |
$this->persistenceManager = $persistenceManager; | |
} | |
/** | |
* We can only convert if the $targetType is either tagged with entity or value object. | |
* | |
* @param mixed $source | |
* @param string $targetType | |
* @return boolean | |
*/ | |
public function canConvertFrom($source, $targetType) { | |
return ( | |
$this->reflectionService->isClassAnnotatedWith($targetType, 'TYPO3\Flow\Annotations\Entity') || | |
$this->reflectionService->isClassAnnotatedWith($targetType, 'TYPO3\Flow\Annotations\ValueObject') || | |
$this->reflectionService->isClassAnnotatedWith($targetType, 'Doctrine\ORM\Mapping\Entity') | |
); | |
} | |
/** | |
* All properties in the source array except __identity are sub-properties. | |
* | |
* @param mixed $source | |
* @return array | |
*/ | |
public function getSourceChildPropertiesToBeConverted($source) { | |
if (is_string($source)) { | |
return array(); | |
} | |
if (isset($source['__identity'])) { | |
unset($source['__identity']); | |
} | |
return parent::getSourceChildPropertiesToBeConverted($source); | |
} | |
/** | |
* The type of a property is determined by the reflection service. | |
* | |
* @param string $targetType | |
* @param string $propertyName | |
* @param \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration | |
* @return string | |
* @throws \TYPO3\Flow\Property\Exception\InvalidTargetException | |
*/ | |
public function getTypeOfChildProperty($targetType, $propertyName, \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration) { | |
$configuredTargetType = $configuration->getConfigurationFor($propertyName)->getConfigurationValue('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', self::CONFIGURATION_TARGET_TYPE); | |
if ($configuredTargetType !== null) { | |
return $configuredTargetType; | |
} | |
/** | |
* In case of a property is notated as the following: | |
* @var \Doctrine\Common\Collections\Collection<\VENDOR\PackageName\Domain\Model\MyAbstractModel> | |
* | |
* and the incoming MyAbstractModel source looks like this: | |
* array( | |
'__type' => \VENDOR\PackageName\Domain\Model\ConcreteModel | |
* ) | |
* | |
* then this __type should be respected by reflectionService to get the correct schema of the concrete model like handleDataArray does, right? | |
* @see convertFrom() LINE 217 | |
* | |
* Examlesnippet from handleArrayData() LINE 217 | |
* | |
* if (isset($source['__type'])) { | |
* if ($configuration->getConfigurationValue('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== TRUE) { | |
* throw new \TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException('Override of target type not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED" to TRUE.', 1317050430); | |
* } | |
* $effectiveTargetType = $source['__type']; | |
* } | |
* | |
*/ | |
$schema = $this->reflectionService->getClassSchema($targetType); | |
if (!$schema->hasProperty($propertyName)) { | |
throw new \TYPO3\Flow\Property\Exception\InvalidTargetException('Property "' . $propertyName . '" was not found in target object of type "' . $targetType . '".', 1297978366); | |
} | |
$propertyInformation = $schema->getProperty($propertyName); | |
return $propertyInformation['type'] . ($propertyInformation['elementType']!==null ? '<' . $propertyInformation['elementType'] . '>' : ''); | |
} | |
/** | |
* Convert an object from $source to an entity or a value object. | |
* | |
* @param mixed $source | |
* @param string $targetType | |
* @param array $convertedChildProperties | |
* @param \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration | |
* @return object the target type | |
* @throws \TYPO3\Flow\Property\Exception\InvalidTargetException | |
* @throws \InvalidArgumentException | |
*/ | |
public function convertFrom($source, $targetType, array $convertedChildProperties = array(), \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration = null) { | |
if (is_array($source)) { | |
if ($this->reflectionService->isClassAnnotatedWith($targetType, 'TYPO3\Flow\Annotations\ValueObject')) { | |
// Unset identity for valueobject to use constructor mapping, since the identity is determined from | |
// constructor arguments | |
unset($source['__identity']); | |
} | |
$object = $this->handleArrayData($source, $targetType, $convertedChildProperties, $configuration); | |
} elseif (is_string($source)) { | |
if ($source === '') { | |
return null; | |
} | |
$object = $this->fetchObjectFromPersistence($source, $targetType); | |
} else { | |
throw new \InvalidArgumentException('Only strings and arrays are accepted.', 1305630314); | |
} | |
foreach ($convertedChildProperties as $propertyName => $propertyValue) { | |
$result = \TYPO3\Flow\Reflection\ObjectAccess::setProperty($object, $propertyName, $propertyValue); | |
if ($result === false) { | |
$exceptionMessage = sprintf( | |
'Property "%s" having a value of type "%s" could not be set in target object of type "%s". Make sure that the property is accessible properly, for example via an appropriate setter method.', | |
$propertyName, | |
(is_object($propertyValue) ? get_class($propertyValue) : gettype($propertyValue)), | |
$targetType | |
); | |
throw new \TYPO3\Flow\Property\Exception\InvalidTargetException($exceptionMessage, 1297935345); | |
} | |
} | |
return $object; | |
} | |
/** | |
* Handle the case if $source is an array. | |
* | |
* @param array $source | |
* @param string $targetType | |
* @param array $convertedChildProperties | |
* @param \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration | |
* @return object | |
* @throws \TYPO3\Flow\Property\Exception\InvalidDataTypeException | |
* @throws \TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException | |
*/ | |
protected function handleArrayData(array $source, $targetType, array &$convertedChildProperties, \TYPO3\Flow\Property\PropertyMappingConfigurationInterface $configuration = null) { | |
$effectiveTargetType = $targetType; | |
if (isset($source['__type'])) { | |
if ($configuration->getConfigurationValue('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== true) { | |
throw new \TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException('Override of target type not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED" to TRUE.', 1317050430); | |
} | |
$effectiveTargetType = $source['__type']; | |
} | |
if (isset($source['__identity'])) { | |
$object = $this->fetchObjectFromPersistence($source['__identity'], $effectiveTargetType); | |
if (count($source) > 1 && ($configuration === null || $configuration->getConfigurationValue('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', self::CONFIGURATION_MODIFICATION_ALLOWED) !== true)) { | |
throw new \TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException('Modification of persistent objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_MODIFICATION_ALLOWED" to TRUE.', 1297932028); | |
} | |
} else { | |
if ($configuration === null || $configuration->getConfigurationValue('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', self::CONFIGURATION_CREATION_ALLOWED) !== true) { | |
throw new \TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException('Creation of objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_CREATION_ALLOWED" to TRUE'); | |
} | |
$object = $this->buildObject($convertedChildProperties, $effectiveTargetType); | |
} | |
if ($effectiveTargetType !== $targetType && !$object instanceof $targetType) { | |
throw new \TYPO3\Flow\Property\Exception\InvalidDataTypeException('The given type "' . $effectiveTargetType . '" is not a subtype of "' . $targetType .'"', 1317048056); | |
} | |
return $object; | |
} | |
/** | |
* Fetch an object from persistence layer. | |
* | |
* @param mixed $identity | |
* @param string $targetType | |
* @return object | |
* @throws \TYPO3\Flow\Property\Exception\TargetNotFoundException | |
* @throws \TYPO3\Flow\Property\Exception\InvalidSourceException | |
*/ | |
protected function fetchObjectFromPersistence($identity, $targetType) { | |
if (is_string($identity)) { | |
$object = $this->persistenceManager->getObjectByIdentifier($identity, $targetType); | |
} elseif (is_array($identity)) { | |
$object = $this->findObjectByIdentityProperties($identity, $targetType); | |
} else { | |
throw new \TYPO3\Flow\Property\Exception\InvalidSourceException('The identity property "' . $identity . '" is neither a string nor an array.', 1297931020); | |
} | |
if ($object === null) { | |
throw new \TYPO3\Flow\Property\Exception\TargetNotFoundException('Object with identity "' . print_r($identity, true) . '" not found.', 1297933823); | |
} | |
return $object; | |
} | |
/** | |
* Finds an object from the repository by searching for its identity properties. | |
* | |
* @param array $identityProperties Property names and values to search for | |
* @param string $type The object type to look for | |
* @return object Either the object matching the identity or NULL if no object was found | |
* @throws \TYPO3\Flow\Property\Exception\DuplicateObjectException if more than one object was found | |
*/ | |
protected function findObjectByIdentityProperties(array $identityProperties, $type) { | |
$query = $this->persistenceManager->createQueryForType($type); | |
$classSchema = $this->reflectionService->getClassSchema($type); | |
$equals = array(); | |
foreach ($classSchema->getIdentityProperties() as $propertyName => $propertyType) { | |
if (isset($identityProperties[$propertyName])) { | |
if ($propertyType === 'string') { | |
$equals[] = $query->equals($propertyName, $identityProperties[$propertyName], false); | |
} else { | |
$equals[] = $query->equals($propertyName, $identityProperties[$propertyName]); | |
} | |
} | |
} | |
if (count($equals) === 1) { | |
$constraint = current($equals); | |
} else { | |
$constraint = $query->logicalAnd(current($equals), next($equals)); | |
while (($equal = next($equals)) !== false) { | |
$constraint = $query->logicalAnd($constraint, $equal); | |
} | |
} | |
$objects = $query->matching($constraint)->execute(); | |
$numberOfResults = $objects->count(); | |
if ($numberOfResults === 1) { | |
return $objects->getFirst(); | |
} elseif ($numberOfResults === 0) { | |
return null; | |
} else { | |
throw new \TYPO3\Flow\Property\Exception\DuplicateObjectException('More than one object was returned for the given identity, this is a constraint violation.', 1259612399); | |
} | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment