/** * Builds a base validator conjunction for the given data type. * * The base validation rules are those which were declared directly in a class (typically * a model) through some validate annotations on properties. * * If a property holds a class for which a base validator exists, that property will be * checked as well, regardless of a validate annotation * * Additionally, if a custom validator was defined for the class in question, it will be added * to the end of the conjunction. A custom validator is found if it follows the naming convention * "Replace '\Model\' by '\Validator\' and append 'Validator'". * * Example: $targetClassName is TYPO3\Foo\Domain\Model\Quux, then the validator will be found if it has the * name TYPO3\Foo\Domain\Validator\QuuxValidator * * @param string $indexKey The key to use as index in $this->baseValidatorConjunctions; calculated from target class name and validation groups * @param string $targetClassName The data type to build the validation conjunction for. Needs to be the fully qualified class name. * @param array $validationGroups The validation groups to build the validator for * @return void * @throws Exception\NoSuchValidatorException * @throws \InvalidArgumentException */ protected function buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups) { $conjunctionValidator = new ConjunctionValidator(); $this->baseValidatorConjunctions[$indexKey] = $conjunctionValidator; if (class_exists($targetClassName)) { // Model based validator $objectValidator = new GenericObjectValidator(array()); foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) { $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($targetClassName, $classPropertyName); try { $parsedType = \TYPO3\FLOW3\Utility\TypeHandling::parseType(trim(implode('', $classPropertyTagsValues['var']), ' \\')); } catch (\TYPO3\FLOW3\Utility\Exception\InvalidTypeException $exception) { throw new \InvalidArgumentException(sprintf(' @var annotation of ' . $exception->getMessage(), 'class "' . $targetClassName . '", property "' . $classPropertyName . '"'), 1315564744); } $propertyTargetClassName = $parsedType['type']; if (\TYPO3\FLOW3\Utility\TypeHandling::isCollectionType($propertyTargetClassName) === TRUE) { $collectionValidator = $this->createValidator('TYPO3\\FLOW3\\Validation\\Validator\\CollectionValidator', array('elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups)); $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator); } elseif (class_exists($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\FLOW3\Object\Configuration\Configuration::SCOPE_PROTOTYPE) { $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $validationGroups); if (count($validatorForProperty) > 0) { $objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty); } } $validateAnnotations = $this->reflectionService->getPropertyAnnotations($targetClassName, $classPropertyName, 'TYPO3\\FLOW3\\Annotations\\Validate'); foreach ($validateAnnotations as $validateAnnotation) { if (count(array_intersect($validateAnnotation->validationGroups, $validationGroups)) === 0) { // In this case, the validation groups for the property do not match current validation context continue; } $newValidator = $this->createValidator($validateAnnotation->type, $validateAnnotation->options); if ($newValidator === NULL) { throw new \TYPO3\FLOW3\Validation\Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validateAnnotation->type . '".', 1241098027); } $objectValidator->addPropertyValidator($classPropertyName, $newValidator); } } if (count($objectValidator->getPropertyValidators()) > 0) { $conjunctionValidator->addValidator($objectValidator); } // Custom validator for the class $possibleValidatorClassName = str_replace('\\Model\\', '\\Validator\\', $targetClassName) . 'Validator'; $customValidator = $this->createValidator($possibleValidatorClassName); if ($customValidator !== NULL) { $conjunctionValidator->addValidator($customValidator); } } }
/** * @test * @dataProvider types */ public function parseTypeReturnsArrayWithInformation($type, $expectedResult) { $this->assertEquals(\TYPO3\FLOW3\Utility\TypeHandling::parseType($type), $expectedResult); }
/** * Adds (defines) a specific property and its type. * * @param string $name Name of the property * @param string $type Type of the property * @param boolean $lazy Whether the property should be lazy-loaded when reconstituting * @return void * @throws \InvalidArgumentException */ public function addProperty($name, $type, $lazy = FALSE) { try { $type = \TYPO3\FLOW3\Utility\TypeHandling::parseType($type); } catch (\TYPO3\FLOW3\Utility\Exception\InvalidTypeException $exception) { throw new \InvalidArgumentException(sprintf($exception->getMessage(), 'class "' . $name . '"'), 1315564474); } $this->properties[$name] = array('type' => $type['type'], 'elementType' => $type['elementType'], 'lazy' => $lazy); }
/** * Adds properties of the class at hand to the class schema. * * Only non-transient properties annotated with a var annotation will be added. * Invalid annotations will cause an exception to be thrown. Properties pointing * to existing classes will only be added if the target type is annotated as * entity or valueobject. * * @param \TYPO3\FLOW3\Reflection\ClassSchema $classSchema * @return void * @throws \InvalidArgumentException * @throws \TYPO3\FLOW3\Reflection\Exception\InvalidPropertyTypeException */ protected function addPropertiesToClassSchema(\TYPO3\FLOW3\Reflection\ClassSchema $classSchema) { // those are added as property even if not tagged with entity/valueobject $propertyTypeWhiteList = array('DateTime', 'SplObjectStorage', 'Doctrine\\Common\\Collections\\Collection', 'Doctrine\\Common\\Collections\\ArrayCollection'); $className = $classSchema->getClassName(); $needsArtificialIdentity = TRUE; foreach ($this->getClassPropertyNames($className) as $propertyName) { if ($this->isPropertyTaggedWith($className, $propertyName, 'var') && !$this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\\FLOW3\\Annotations\\Transient')) { $declaredType = trim(implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), ' \\'); if (preg_match('/\\s/', $declaredType) === 1) { throw new \TYPO3\FLOW3\Reflection\Exception\InvalidPropertyTypeException('The @var annotation for "' . $className . '::$' . $propertyName . '" seems to be invalid.', 1284132314); } if ($this->isPropertyAnnotatedWith($className, $propertyName, 'Doctrine\\ORM\\Mapping\\Id')) { $needsArtificialIdentity = FALSE; } try { $parsedType = \TYPO3\FLOW3\Utility\TypeHandling::parseType($declaredType); } catch (\TYPO3\FLOW3\Utility\Exception\InvalidTypeException $exception) { throw new \InvalidArgumentException(sprintf($exception->getMessage(), 'class "' . $className . '" for property "' . $propertyName . '"'), 1315564475); } if (!in_array($parsedType['type'], $propertyTypeWhiteList) && (class_exists($parsedType['type']) || interface_exists($parsedType['type'])) && !($this->isClassAnnotatedWith($parsedType['type'], 'TYPO3\\FLOW3\\Annotations\\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'Doctrine\\ORM\\Mapping\\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'TYPO3\\FLOW3\\Annotations\\ValueObject'))) { continue; } $classSchema->addProperty($propertyName, $declaredType, $this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\\FLOW3\\Annotations\\Lazy')); if ($this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\\FLOW3\\Annotations\\Identity')) { $classSchema->markAsIdentityProperty($propertyName); } } } if ($needsArtificialIdentity === TRUE) { $classSchema->addProperty('FLOW3_Persistence_Identifier', 'string'); } }
/** * Return the type of a given sub-property inside the $targetType * * @param string $targetType * @param string $propertyName * @param \TYPO3\FLOW3\Property\PropertyMappingConfigurationInterface $configuration * @return string * @api */ public function getTypeOfChildProperty($targetType, $propertyName, \TYPO3\FLOW3\Property\PropertyMappingConfigurationInterface $configuration) { $parsedTargetType = \TYPO3\FLOW3\Utility\TypeHandling::parseType($targetType); return $parsedTargetType['elementType']; }