/** * @param string $modelClassName * @param array $properties * @param string $applicationName * @param array $foundRelations * @return string */ public function getModelDeclaration($modelClassName, array $properties, $applicationName, array &$foundRelations) { $globalPropertyIgnoreConfiguration = $this->configurationManager->getConfiguration('Settings', 'Radmiraal.Emberjs.properties._exclude'); $modelGenerationPropertyIgnoreConfiguration = $this->configurationManager->getConfiguration('Settings', 'Radmiraal.Emberjs.applications.' . $applicationName . '.model.ignoredProperties'); $ignoredProperties = array_merge($globalPropertyIgnoreConfiguration ?: array(), $modelGenerationPropertyIgnoreConfiguration ?: array()); $typeMapping = $this->configurationManager->getConfiguration('Settings', 'Radmiraal.Emberjs.applications.' . $applicationName . '.model.typeMapping'); $propertyDefinition = array(); foreach ($properties as $property => $propertyConfiguration) { if ($property === 'id' || in_array($property, $ignoredProperties) || $this->reflectionService->isPropertyAnnotatedWith($modelClassName, $property, 'TYPO3\\Flow\\Annotations\\Transient')) { continue; } $type = $propertyConfiguration['type']; if (isset($typeMapping[$type])) { $type = $typeMapping[$type]; } if (strpos($propertyConfiguration['type'], '\\')) { if (!in_array($propertyConfiguration['type'], $foundRelations)) { $foundRelations[] = $propertyConfiguration['type']; } $type = 'App.' . EmberDataUtility::uncamelizeClassName($type); } $propertyDefinition[] = sprintf('%s%s: DS.attr("%s")', chr(10) . "\t", $property, !empty($type) ? $type : 'string'); } $modelName = EmberDataUtility::uncamelizeClassName($modelClassName); return sprintf('App.%s = DS.Model.extend(App.ModelMixin, {%s%s});', $modelName, implode(',', $propertyDefinition), chr(10)) . chr(10); }
/** * 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)) { $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) { if ($this->reflectionService->isPropertyAnnotatedWith($targetType, $propertyName, 'Doctrine\\ODM\\CouchDB\\Mapping\\Annotations\\Attachments')) { $attachments = array(); foreach ($propertyValue as $version => $value) { $safeFileName = preg_replace('/[^a-zA-Z-_0-9\\.]*/', '', $value['name']); $attachments[$safeFileName] = \Doctrine\CouchDB\Attachment::createFromBinaryData(\TYPO3\Flow\Utility\Files::getFileContents($value['tmp_name'])); } $propertyValue = $attachments; } $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; }
/** * After returning advice, generates the value hash for the object * * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point * @return void * @Flow\After("classAnnotatedWith(TYPO3\Flow\Annotations\ValueObject) && method(.*->__construct()) && filter(TYPO3\Flow\Persistence\Doctrine\Mapping\Driver\FlowAnnotationDriver)") */ public function generateValueHash(JoinPointInterface $joinPoint) { $proxy = $joinPoint->getProxy(); $proxyClassName = get_class($proxy); $hashSourceParts = array(); $properties = $this->reflectionService->getClassPropertyNames($proxyClassName); foreach ($properties as $property) { // Currently, private properties are transient. Should this behaviour change, they need to be included // in the value hash generation if ($this->reflectionService->isPropertyAnnotatedWith($proxyClassName, $property, 'TYPO3\\Flow\\Annotations\\Transient') || $this->reflectionService->isPropertyPrivate($proxyClassName, $property)) { continue; } $propertyValue = ObjectAccess::getProperty($proxy, $property, TRUE); if (is_object($propertyValue) === TRUE) { // The persistence manager will return NULL if the given object is unknown to persistence $propertyValue = $this->persistenceManager->getIdentifierByObject($propertyValue) ?: $propertyValue; } $hashSourceParts[$property] = $propertyValue; } ksort($hashSourceParts); $hashSourceParts['__class_name__'] = $proxyClassName; $serializedSource = $this->useIgBinary === TRUE ? igbinary_serialize($hashSourceParts) : serialize($hashSourceParts); $proxy = $joinPoint->getProxy(); ObjectAccess::setProperty($proxy, 'Persistence_Object_Identifier', sha1($serializedSource), TRUE); }
/** * Builds methods for a single AOP proxy class for the specified class. * * @param string $targetClassName Name of the class to create a proxy class file for * @param array &$aspectContainers The array of aspect containers from the AOP Framework * @return boolean TRUE if the proxy class could be built, FALSE otherwise. */ public function buildProxyClass($targetClassName, array &$aspectContainers) { $interfaceIntroductions = $this->getMatchingInterfaceIntroductions($aspectContainers, $targetClassName); $introducedInterfaces = $this->getInterfaceNamesFromIntroductions($interfaceIntroductions); $propertyIntroductions = $this->getMatchingPropertyIntroductions($aspectContainers, $targetClassName); $methodsFromTargetClass = $this->getMethodsFromTargetClass($targetClassName); $methodsFromIntroducedInterfaces = $this->getIntroducedMethodsFromInterfaceIntroductions($interfaceIntroductions, $targetClassName); $interceptedMethods = array(); $this->addAdvicedMethodsToInterceptedMethods($interceptedMethods, array_merge($methodsFromTargetClass, $methodsFromIntroducedInterfaces), $targetClassName, $aspectContainers); $this->addIntroducedMethodsToInterceptedMethods($interceptedMethods, $methodsFromIntroducedInterfaces); if (count($interceptedMethods) < 1 && count($introducedInterfaces) < 1 && count($propertyIntroductions) < 1) { return FALSE; } $proxyClass = $this->compiler->getProxyClass($targetClassName); if ($proxyClass === FALSE) { return FALSE; } $proxyClass->addInterfaces($introducedInterfaces); /** @var $propertyIntroduction PropertyIntroduction */ foreach ($propertyIntroductions as $propertyIntroduction) { $propertyName = $propertyIntroduction->getPropertyName(); $declaringAspectClassName = $propertyIntroduction->getDeclaringAspectClassName(); $possiblePropertyTypes = $this->reflectionService->getPropertyTagValues($declaringAspectClassName, $propertyName, 'var'); if (count($possiblePropertyTypes) > 0 && !$this->reflectionService->isPropertyAnnotatedWith($declaringAspectClassName, $propertyName, 'TYPO3\\Flow\\Annotations\\Transient')) { $classSchema = $this->reflectionService->getClassSchema($targetClassName); if ($classSchema !== NULL) { $classSchema->addProperty($propertyName, $possiblePropertyTypes[0]); } } $propertyReflection = new PropertyReflection($declaringAspectClassName, $propertyName); $propertyReflection->setIsAopIntroduced(TRUE); $this->reflectionService->reflectClassProperty($targetClassName, $propertyReflection, new ClassReflection($declaringAspectClassName)); $proxyClass->addProperty($propertyName, 'NULL', $propertyIntroduction->getPropertyVisibility(), $propertyIntroduction->getPropertyDocComment()); } $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode("\t\tif (method_exists(get_parent_class(\$this), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable('parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray')) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"); $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode($this->buildMethodsAndAdvicesArrayCode($interceptedMethods)); $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->overrideMethodVisibility('protected'); $callBuildMethodsAndAdvicesArrayCode = "\n\t\t\$this->Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"; $proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); $proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); if (!$this->reflectionService->hasMethod($targetClassName, '__wakeup')) { $proxyClass->getMethod('__wakeup')->addPostParentCallCode("\t\tif (method_exists(get_parent_class(\$this), '__wakeup') && is_callable('parent::__wakeup')) parent::__wakeup();\n"); } // FIXME this can be removed again once Doctrine is fixed (see fixMethodsAndAdvicesArrayForDoctrineProxiesCode()) $proxyClass->getMethod('Flow_Aop_Proxy_fixMethodsAndAdvicesArrayForDoctrineProxies')->addPreParentCallCode($this->fixMethodsAndAdvicesArrayForDoctrineProxiesCode()); // FIXME this can be removed again once Doctrine is fixed (see fixInjectedPropertiesForDoctrineProxiesCode()) $proxyClass->getMethod('Flow_Aop_Proxy_fixInjectedPropertiesForDoctrineProxies')->addPreParentCallCode($this->fixInjectedPropertiesForDoctrineProxiesCode()); $this->buildGetAdviceChainsMethodCode($targetClassName); $this->buildInvokeJoinPointMethodCode($targetClassName); $this->buildMethodsInterceptorCode($targetClassName, $interceptedMethods); $proxyClass->addProperty('Flow_Aop_Proxy_targetMethodsAndGroupedAdvices', 'array()'); $proxyClass->addProperty('Flow_Aop_Proxy_groupedAdviceChains', 'array()'); $proxyClass->addProperty('Flow_Aop_Proxy_methodIsInAdviceMode', 'array()'); return TRUE; }
/** * 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 Neos\Foo\Domain\Model\Quux, then the validator will be found if it has the * name Neos\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 (!TypeHandling::isSimpleType($targetClassName) && class_exists($targetClassName)) { // Model based validator $classSchema = $this->reflectionService->getClassSchema($targetClassName); if ($classSchema !== null && $classSchema->isAggregateRoot()) { $objectValidator = new AggregateBoundaryValidator(array()); } else { $objectValidator = new GenericObjectValidator([]); } $conjunctionValidator->addValidator($objectValidator); foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) { $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($targetClassName, $classPropertyName); if (!isset($classPropertyTagsValues['var'])) { throw new \InvalidArgumentException(sprintf('There is no @var annotation for property "%s" in class "%s".', $classPropertyName, $targetClassName), 1363778104); } try { $parsedType = TypeHandling::parseType(trim(implode('', $classPropertyTagsValues['var']), ' \\')); } catch (InvalidTypeException $exception) { throw new \InvalidArgumentException(sprintf(' @var annotation of ' . $exception->getMessage(), 'class "' . $targetClassName . '", property "' . $classPropertyName . '"'), 1315564744, $exception); } if ($this->reflectionService->isPropertyAnnotatedWith($targetClassName, $classPropertyName, Flow\IgnoreValidation::class)) { continue; } $propertyTargetClassName = $parsedType['type']; if (TypeHandling::isCollectionType($propertyTargetClassName) === true) { $collectionValidator = $this->createValidator(Validator\CollectionValidator::class, ['elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups]); $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator); } elseif (!TypeHandling::isSimpleType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === Configuration::SCOPE_PROTOTYPE) { $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $validationGroups); if (count($validatorForProperty) > 0) { $objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty); } } $validateAnnotations = $this->reflectionService->getPropertyAnnotations($targetClassName, $classPropertyName, Flow\Validate::class); 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 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->removeValidator($objectValidator); } } $this->addCustomValidators($targetClassName, $conjunctionValidator); }
/** * Builds methods for a single AOP proxy class for the specified class. * * @param string $targetClassName Name of the class to create a proxy class file for * @param array &$aspectContainers The array of aspect containers from the AOP Framework * @return boolean TRUE if the proxy class could be built, FALSE otherwise. */ public function buildProxyClass($targetClassName, array &$aspectContainers) { $interfaceIntroductions = $this->getMatchingInterfaceIntroductions($aspectContainers, $targetClassName); $introducedInterfaces = $this->getInterfaceNamesFromIntroductions($interfaceIntroductions); $introducedTraits = $this->getMatchingTraitNamesFromIntroductions($aspectContainers, $targetClassName); $propertyIntroductions = $this->getMatchingPropertyIntroductions($aspectContainers, $targetClassName); $methodsFromTargetClass = $this->getMethodsFromTargetClass($targetClassName); $methodsFromIntroducedInterfaces = $this->getIntroducedMethodsFromInterfaceIntroductions($interfaceIntroductions, $targetClassName); $interceptedMethods = array(); $this->addAdvicedMethodsToInterceptedMethods($interceptedMethods, array_merge($methodsFromTargetClass, $methodsFromIntroducedInterfaces), $targetClassName, $aspectContainers); $this->addIntroducedMethodsToInterceptedMethods($interceptedMethods, $methodsFromIntroducedInterfaces); if (count($interceptedMethods) < 1 && count($introducedInterfaces) < 1 && count($propertyIntroductions) < 1) { return false; } $proxyClass = $this->compiler->getProxyClass($targetClassName); if ($proxyClass === false) { return false; } $proxyClass->addInterfaces($introducedInterfaces); $proxyClass->addTraits($introducedTraits); /** @var $propertyIntroduction PropertyIntroduction */ foreach ($propertyIntroductions as $propertyIntroduction) { $propertyName = $propertyIntroduction->getPropertyName(); $declaringAspectClassName = $propertyIntroduction->getDeclaringAspectClassName(); $possiblePropertyTypes = $this->reflectionService->getPropertyTagValues($declaringAspectClassName, $propertyName, 'var'); if (count($possiblePropertyTypes) > 0 && !$this->reflectionService->isPropertyAnnotatedWith($declaringAspectClassName, $propertyName, \TYPO3\Flow\Annotations\Transient::class)) { $classSchema = $this->reflectionService->getClassSchema($targetClassName); if ($classSchema !== null) { $classSchema->addProperty($propertyName, $possiblePropertyTypes[0]); } } $propertyReflection = new PropertyReflection($declaringAspectClassName, $propertyName); $propertyReflection->setIsAopIntroduced(true); $this->reflectionService->reflectClassProperty($targetClassName, $propertyReflection, new ClassReflection($declaringAspectClassName)); $proxyClass->addProperty($propertyName, var_export($propertyIntroduction->getInitialValue(), true), $propertyIntroduction->getPropertyVisibility(), $propertyIntroduction->getPropertyDocComment()); } $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode(" if (method_exists(get_parent_class(), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable('parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray')) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"); $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode($this->buildMethodsAndAdvicesArrayCode($interceptedMethods)); $proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->overrideMethodVisibility('protected'); $callBuildMethodsAndAdvicesArrayCode = "\n \$this->Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"; $proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); $proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); $proxyClass->getMethod('__clone')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); if (!$this->reflectionService->hasMethod($targetClassName, '__wakeup')) { $proxyClass->getMethod('__wakeup')->addPostParentCallCode(" if (method_exists(get_parent_class(), '__wakeup') && is_callable('parent::__wakeup')) parent::__wakeup();\n"); } $proxyClass->addTraits(['\\' . AdvicesTrait::class]); $this->buildMethodsInterceptorCode($targetClassName, $interceptedMethods); $proxyClass->addProperty('Flow_Aop_Proxy_targetMethodsAndGroupedAdvices', 'array()'); $proxyClass->addProperty('Flow_Aop_Proxy_groupedAdviceChains', 'array()'); $proxyClass->addProperty('Flow_Aop_Proxy_methodIsInAdviceMode', 'array()'); return true; }
/** * Evaluate the property annotations and amend the metadata accordingly. * * @param ClassMetadataInfo $metadata * @return void * @throws MappingException */ protected function evaluatePropertyAnnotations(ClassMetadataInfo $metadata) { $className = $metadata->name; $class = $metadata->getReflectionClass(); $classSchema = $this->getClassSchema($className); foreach ($class->getProperties() as $property) { if (!$classSchema->hasProperty($property->getName()) || $classSchema->isPropertyTransient($property->getName()) || $metadata->isMappedSuperclass && !$property->isPrivate() || $metadata->isInheritedField($property->getName()) || $metadata->isInheritedAssociation($property->getName())) { continue; } $propertyMetaData = $classSchema->getProperty($property->getName()); $mapping = array(); $mapping['fieldName'] = $property->getName(); $mapping['columnName'] = strtolower($property->getName()); $mapping['targetEntity'] = $propertyMetaData['type']; $joinColumns = $this->evaluateJoinColumnAnnotations($property); // Field can only be annotated with one of: // @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Column (optional) if ($oneToOneAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\OneToOne')) { if ($oneToOneAnnotation->targetEntity) { $mapping['targetEntity'] = $oneToOneAnnotation->targetEntity; } if ($oneToOneAnnotation->inversedBy !== null || $oneToOneAnnotation->mappedBy === null) { $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property); } $mapping['mappedBy'] = $oneToOneAnnotation->mappedBy; $mapping['inversedBy'] = $oneToOneAnnotation->inversedBy; if ($oneToOneAnnotation->cascade) { $mapping['cascade'] = $oneToOneAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = array('persist'); } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = array('all'); } if ($oneToOneAnnotation->orphanRemoval) { $mapping['orphanRemoval'] = $oneToOneAnnotation->orphanRemoval; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnotation->fetch); $metadata->mapOneToOne($mapping); } elseif ($oneToManyAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\OneToMany')) { $mapping['mappedBy'] = $oneToManyAnnotation->mappedBy; if ($oneToManyAnnotation->targetEntity) { $mapping['targetEntity'] = $oneToManyAnnotation->targetEntity; } elseif (isset($propertyMetaData['elementType'])) { $mapping['targetEntity'] = $propertyMetaData['elementType']; } if ($oneToManyAnnotation->cascade) { $mapping['cascade'] = $oneToManyAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = array('persist'); } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = array('all'); } $mapping['indexBy'] = $oneToManyAnnotation->indexBy; if ($oneToManyAnnotation->orphanRemoval) { $mapping['orphanRemoval'] = $oneToManyAnnotation->orphanRemoval; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnotation->fetch); if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\OrderBy')) { $mapping['orderBy'] = $orderByAnnotation->value; } $metadata->mapOneToMany($mapping); } elseif ($manyToOneAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\ManyToOne')) { if ($manyToOneAnnotation->targetEntity) { $mapping['targetEntity'] = $manyToOneAnnotation->targetEntity; } $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property); if ($manyToOneAnnotation->cascade) { $mapping['cascade'] = $manyToOneAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = array('persist'); } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = array('all'); } $mapping['inversedBy'] = $manyToOneAnnotation->inversedBy; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnotation->fetch); $metadata->mapManyToOne($mapping); } elseif ($manyToManyAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\ManyToMany')) { if ($manyToManyAnnotation->targetEntity) { $mapping['targetEntity'] = $manyToManyAnnotation->targetEntity; } elseif (isset($propertyMetaData['elementType'])) { $mapping['targetEntity'] = $propertyMetaData['elementType']; } /** @var JoinTable $joinTableAnnotation */ if ($joinTableAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\JoinTable')) { $joinTable = $this->evaluateJoinTableAnnotation($joinTableAnnotation, $property, $className, $mapping); } else { $joinColumns = array(array('name' => null, 'referencedColumnName' => null)); $joinTable = array('name' => $this->inferJoinTableNameFromClassAndPropertyName($className, $property->getName()), 'joinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property, self::MAPPING_MM_REGULAR), 'inverseJoinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property)); } $mapping['joinTable'] = $joinTable; $mapping['mappedBy'] = $manyToManyAnnotation->mappedBy; $mapping['inversedBy'] = $manyToManyAnnotation->inversedBy; if ($manyToManyAnnotation->cascade) { $mapping['cascade'] = $manyToManyAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = array('persist'); } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = array('all'); } $mapping['indexBy'] = $manyToManyAnnotation->indexBy; $mapping['orphanRemoval'] = $manyToManyAnnotation->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnotation->fetch); if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\OrderBy')) { $mapping['orderBy'] = $orderByAnnotation->value; } $metadata->mapManyToMany($mapping); } else { $mapping['nullable'] = false; /** @var Column $columnAnnotation */ if ($columnAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\Column')) { $mapping = $this->addColumnToMappingArray($columnAnnotation, $mapping); } if (!isset($mapping['type'])) { switch ($propertyMetaData['type']) { case 'DateTime': $mapping['type'] = 'datetime'; break; case 'string': case 'integer': case 'boolean': case 'float': case 'array': $mapping['type'] = $propertyMetaData['type']; break; default: if (strpos($propertyMetaData['type'], '\\') !== false) { if ($this->reflectionService->isClassAnnotatedWith($propertyMetaData['type'], \TYPO3\Flow\Annotations\ValueObject::class)) { $mapping['type'] = 'object'; } elseif (class_exists($propertyMetaData['type'])) { throw MappingException::missingRequiredOption($property->getName(), 'OneToOne', sprintf('The property "%s" in class "%s" has a non standard data type and doesn\'t define the type of the relation. You have to use one of these annotations: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany', $property->getName(), $className)); } } else { throw MappingException::propertyTypeIsRequired($className, $property->getName()); } } } if ($this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\Id') !== null) { $mapping['id'] = true; } if ($generatedValueAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\GeneratedValue')) { $metadata->setIdGeneratorType(constant('Doctrine\\ORM\\Mapping\\ClassMetadata::GENERATOR_TYPE_' . strtoupper($generatedValueAnnotation->strategy))); } if ($this->reflectionService->isPropertyAnnotatedWith($className, $property->getName(), 'Doctrine\\ORM\\Mapping\\Version')) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition if ($seqGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\SequenceGenerator')) { $metadata->setSequenceGeneratorDefinition(array('sequenceName' => $seqGeneratorAnnotation->sequenceName, 'allocationSize' => $seqGeneratorAnnotation->allocationSize, 'initialValue' => $seqGeneratorAnnotation->initialValue)); } elseif ($this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\TableGenerator') !== null) { throw MappingException::tableIdGeneratorNotImplemented($className); } elseif ($customGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, 'Doctrine\\ORM\\Mapping\\CustomIdGenerator')) { $metadata->setCustomGeneratorDefinition(array('class' => $customGeneratorAnnotation->class)); } } } }
/** * Evaluate the property annotations and amend the metadata accordingly. * * @param ORM\ClassMetadataInfo $metadata * @return void * @throws ORM\MappingException */ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) { $className = $metadata->name; $class = $metadata->getReflectionClass(); $classSchema = $this->getClassSchema($className); foreach ($class->getProperties() as $property) { if (!$classSchema->hasProperty($property->getName()) || $classSchema->isPropertyTransient($property->getName()) || $metadata->isMappedSuperclass && !$property->isPrivate() || $metadata->isInheritedField($property->getName()) || $metadata->isInheritedAssociation($property->getName()) || $metadata->isInheritedEmbeddedClass($property->getName())) { continue; } $propertyMetaData = $classSchema->getProperty($property->getName()); $mapping = []; $mapping['fieldName'] = $property->getName(); $mapping['columnName'] = strtolower($property->getName()); $mapping['targetEntity'] = $propertyMetaData['type']; $joinColumns = $this->evaluateJoinColumnAnnotations($property); // Field can only be annotated with one of: // @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Column (optional) if ($oneToOneAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OneToOne::class)) { if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) { $mapping['id'] = true; } if ($oneToOneAnnotation->targetEntity) { $mapping['targetEntity'] = $oneToOneAnnotation->targetEntity; } if ($oneToOneAnnotation->inversedBy !== null || $oneToOneAnnotation->mappedBy === null) { $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property); } $mapping['mappedBy'] = $oneToOneAnnotation->mappedBy; $mapping['inversedBy'] = $oneToOneAnnotation->inversedBy; if ($oneToOneAnnotation->cascade) { $mapping['cascade'] = $oneToOneAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } if ($oneToOneAnnotation->orphanRemoval) { $mapping['orphanRemoval'] = $oneToOneAnnotation->orphanRemoval; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnotation->fetch); $metadata->mapOneToOne($mapping); } elseif ($oneToManyAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OneToMany::class)) { $mapping['mappedBy'] = $oneToManyAnnotation->mappedBy; if ($oneToManyAnnotation->targetEntity) { $mapping['targetEntity'] = $oneToManyAnnotation->targetEntity; } elseif (isset($propertyMetaData['elementType'])) { $mapping['targetEntity'] = $propertyMetaData['elementType']; } if ($oneToManyAnnotation->cascade) { $mapping['cascade'] = $oneToManyAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } $mapping['indexBy'] = $oneToManyAnnotation->indexBy; if ($oneToManyAnnotation->orphanRemoval) { $mapping['orphanRemoval'] = $oneToManyAnnotation->orphanRemoval; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnotation->fetch); if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OrderBy::class)) { $mapping['orderBy'] = $orderByAnnotation->value; } $metadata->mapOneToMany($mapping); } elseif ($manyToOneAnnotation = $this->reader->getPropertyAnnotation($property, ORM\ManyToOne::class)) { if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) { $mapping['id'] = true; } if ($manyToOneAnnotation->targetEntity) { $mapping['targetEntity'] = $manyToOneAnnotation->targetEntity; } $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property); if ($manyToOneAnnotation->cascade) { $mapping['cascade'] = $manyToOneAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } $mapping['inversedBy'] = $manyToOneAnnotation->inversedBy; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnotation->fetch); $metadata->mapManyToOne($mapping); } elseif ($manyToManyAnnotation = $this->reader->getPropertyAnnotation($property, ORM\ManyToMany::class)) { if ($manyToManyAnnotation->targetEntity) { $mapping['targetEntity'] = $manyToManyAnnotation->targetEntity; } elseif (isset($propertyMetaData['elementType'])) { $mapping['targetEntity'] = $propertyMetaData['elementType']; } /** @var ORM\JoinTable $joinTableAnnotation */ if ($joinTableAnnotation = $this->reader->getPropertyAnnotation($property, ORM\JoinTable::class)) { $joinTable = $this->evaluateJoinTableAnnotation($joinTableAnnotation, $property, $className, $mapping); } else { $joinColumns = [['name' => null, 'referencedColumnName' => null]]; $joinTable = ['name' => $this->inferJoinTableNameFromClassAndPropertyName($className, $property->getName()), 'joinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property, self::MAPPING_MM_REGULAR), 'inverseJoinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property)]; } $mapping['joinTable'] = $joinTable; $mapping['mappedBy'] = $manyToManyAnnotation->mappedBy; $mapping['inversedBy'] = $manyToManyAnnotation->inversedBy; if ($manyToManyAnnotation->cascade) { $mapping['cascade'] = $manyToManyAnnotation->cascade; } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } $mapping['indexBy'] = $manyToManyAnnotation->indexBy; $mapping['orphanRemoval'] = $manyToManyAnnotation->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnotation->fetch); if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OrderBy::class)) { $mapping['orderBy'] = $orderByAnnotation->value; } $metadata->mapManyToMany($mapping); } elseif ($embeddedAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Embedded::class)) { if ($embeddedAnnotation->class) { $mapping['class'] = $embeddedAnnotation->class; } else { // This will not happen currently, because "class" argument is required. It would be nice if that could be changed though. $mapping['class'] = $mapping['targetEntity']; } $mapping['columnPrefix'] = $embeddedAnnotation->columnPrefix; $metadata->mapEmbedded($mapping); } else { $mapping['nullable'] = false; /** @var ORM\Column $columnAnnotation */ if ($columnAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Column::class)) { $mapping = $this->addColumnToMappingArray($columnAnnotation, $mapping); } if (!isset($mapping['type'])) { switch ($propertyMetaData['type']) { case 'DateTime': $mapping['type'] = 'datetime'; break; case 'string': case 'integer': case 'boolean': case 'float': case 'array': $mapping['type'] = $propertyMetaData['type']; break; default: if (strpos($propertyMetaData['type'], '\\') !== false) { if ($this->reflectionService->isClassAnnotatedWith($propertyMetaData['type'], Flow\ValueObject::class)) { $valueObjectAnnotation = $this->reflectionService->getClassAnnotation($propertyMetaData['type'], Flow\ValueObject::class); if ($valueObjectAnnotation->embedded === true) { $mapping['class'] = $propertyMetaData['type']; $mapping['columnPrefix'] = $mapping['columnName']; $metadata->mapEmbedded($mapping); // Leave switch and continue with next property continue 2; } $mapping['type'] = 'object'; } elseif (class_exists($propertyMetaData['type'])) { throw ORM\MappingException::missingRequiredOption($property->getName(), 'OneToOne', sprintf('The property "%s" in class "%s" has a non standard data type and doesn\'t define the type of the relation. You have to use one of these annotations: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany', $property->getName(), $className)); } } else { throw ORM\MappingException::propertyTypeIsRequired($className, $property->getName()); } } } if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) { $mapping['id'] = true; } if ($generatedValueAnnotation = $this->reader->getPropertyAnnotation($property, ORM\GeneratedValue::class)) { $metadata->setIdGeneratorType(constant('Doctrine\\ORM\\Mapping\\ClassMetadata::GENERATOR_TYPE_' . strtoupper($generatedValueAnnotation->strategy))); } if ($this->reflectionService->isPropertyAnnotatedWith($className, $property->getName(), ORM\Version::class)) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition if ($seqGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\SequenceGenerator::class)) { $metadata->setSequenceGeneratorDefinition(['sequenceName' => $seqGeneratorAnnotation->sequenceName, 'allocationSize' => $seqGeneratorAnnotation->allocationSize, 'initialValue' => $seqGeneratorAnnotation->initialValue]); } elseif ($this->reader->getPropertyAnnotation($property, ORM\TableGenerator::class) !== null) { throw ORM\MappingException::tableIdGeneratorNotImplemented($className); } elseif ($customGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\CustomIdGenerator::class)) { $metadata->setCustomGeneratorDefinition(['class' => $customGeneratorAnnotation->class]); } } // Evaluate @Cache annotation if (($cacheAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Cache::class)) !== null) { $metadata->enableAssociationCache($mapping['fieldName'], array('usage' => constant('Doctrine\\ORM\\Mapping\\ClassMetadata::CACHE_USAGE_' . $cacheAnnotation->usage), 'region' => $cacheAnnotation->region)); } } }
/** * Returns TRUE if the property of an object should be ignored * * @param string $objectName * @param string $propertyName * @return boolean */ protected function isPropertyIgnored($objectName, $propertyName) { return $propertyName === 'Persistence_Object_Identifier' || substr($propertyName, 0, 2) === '__' || in_array($propertyName, isset($this->configuration['ignoredProperties']) ? $this->configuration['ignoredProperties'] : array()) || $this->reflectionService->isPropertyAnnotatedWith($objectName, $propertyName, 'TYPO3\\Flow\\Annotations\\Transient'); }