/** * @throws RuntimeException * @param string $className * @param string $propertyName * @return string */ public function getTargetEntity($className, $propertyName) { $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName); if (!empty($propertyMetaData['elementType'])) { $type = $propertyMetaData['elementType']; } elseif (!empty($propertyMetaData['type'])) { $type = $propertyMetaData['type']; } else { throw new \RuntimeException("Cannot guess target entity from {$className}#{$propertyName} using Extbase metadata."); } return $type; }
/** * Creates the remote api based on the module/plugin configuration using the extbase * reflection features. * * @param string $routeUrl * @param string $namespace * @return array */ protected function createApi($routeUrl, $namespace) { $api = array(); $api['url'] = $routeUrl; $api['type'] = 'remoting'; $api['namespace'] = $namespace; $api['actions'] = array(); if (empty($this->frameworkConfiguration['controllerConfiguration'])) { # @todo improve me! Hack for fetching API of newsletter the hard way! # It looks $this->frameworkConfiguration['controllerConfiguration'] is empty as of TYPO3 6.1. Bug or feature? $this->frameworkConfiguration['controllerConfiguration'] = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions']['Newsletter']['modules']['web_NewsletterTxNewsletterM1']['controllers']; } foreach ($this->frameworkConfiguration['controllerConfiguration'] as $controllerName => $allowedControllerActions) { $unstrippedControllerName = $controllerName . 'Controller'; $controllerObjectName = 'Tx_' . $this->frameworkConfiguration['extensionName'] . '_Controller_' . $unstrippedControllerName; $controllerActions = array(); foreach ($allowedControllerActions['actions'] as $actionName) { $unstrippedActionName = $actionName . 'Action'; try { $actionParameters = $this->reflectionService->getMethodParameters($controllerObjectName, $unstrippedActionName); $controllerActions[] = array('len' => count($actionParameters), 'name' => $unstrippedActionName); } catch (ReflectionException $re) { if ($unstrippedActionName !== 'extObjAction') { \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('You have a not existing action (' . $controllerObjectName . '::' . $unstrippedActionName . ') in your module/plugin configuration. This action will not be available for Ext.Direct remote execution.', 'Newsletter', 1); } } } $api['actions'][$unstrippedControllerName] = $controllerActions; } return $api; }
/** * Copy a singe object based on field annotations about how to copy the object * * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be copied * @return Tx_Extbase_DomainObject_DomainObjectInterface $copy * @api */ public function copy($object) { $className = get_class($object); $this->recursionService->in(); $this->recursionService->check($className); $copy = $this->objectManager->get($className); $properties = $this->reflectionService->getClassPropertyNames($className); foreach ($properties as $propertyName) { $tags = $this->reflectionService->getPropertyTagsValues($className, $propertyName); $getter = 'get' . ucfirst($propertyName); $setter = 'set' . ucfirst($propertyName); $copyMethod = $tags['copy'][0]; $copiedValue = NULL; if ($copyMethod !== NULL && $copyMethod !== 'ignore') { $originalValue = $object->{$getter}(); if ($copyMethod == 'reference') { $copiedValue = $this->copyAsReference($originalValue); } elseif ($copyMethod == 'clone') { $copiedValue = $this->copyAsClone($originalValue); } if ($copiedValue != NULL) { $copy->{$setter}($copiedValue); } } } $this->recursionService->out(); return $copy; }
/** * Initializes the Reflection Service * * @return void */ protected function initializeReflection() { self::$reflectionService = t3lib_div::makeInstance('Tx_Extbase_Reflection_Service'); self::$reflectionService->setCache($this->cacheManager->getCache('cache_extbase_reflection')); if (!self::$reflectionService->isInitialized()) { self::$reflectionService->initialize(); } }
/** * Builds a data map by adding column maps for all the configured columns in the $TCA. * It also resolves the type of values the column is holding and the typo of relation the column * represents. * * @param string $className The class name you want to fetch the Data Map for * @return Tx_Extbase_Persistence_Mapper_DataMap The data map */ public function buildDataMap($className) { if (!class_exists($className)) { throw new Tx_Extbase_Persistence_Exception_InvalidClass('Could not find class definition for name "' . $className . '". This could be caused by a mis-spelling of the class name in the class definition.'); } $recordType = NULL; $subclasses = array(); $tableName = strtolower($className); $columnMapping = array(); $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration(); $classSettings = $extbaseFrameworkConfiguration['persistence']['classes'][$className]; if ($classSettings !== NULL) { if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) { $subclasses = $classSettings['subclasses']; } if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) { $recordType = $classSettings['mapping']['recordType']; } if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) { $tableName = $classSettings['mapping']['tableName']; } $classHierachy = array_merge(array($className), class_parents($className)); foreach ($classHierachy as $currentClassName) { if (in_array($currentClassName, array('Tx_Extbase_DomainObject_AbstractEntity', 'Tx_Extbase_DomainObject_AbstractValueObject'))) { break; } $currentTableName = strtolower($currentClassName); $currentClassSettings = $extbaseFrameworkConfiguration['persistence']['classes'][$currentClassName]; if ($currentClassSettings !== NULL) { if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) { $columnMapping = t3lib_div::array_merge_recursive_overrule($columnMapping, $currentClassSettings['mapping']['columns'], 0, FALSE); // FALSE means: do not include empty values form 2nd array } } } } $dataMap = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMap', $className, $tableName, $recordType, $subclasses); $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName); // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className); $tcaColumnsDefinition = $this->getColumnsDefinition($tableName); $tcaColumnsDefinition = t3lib_div::array_merge_recursive_overrule($tcaColumnsDefinition, $columnMapping); // TODO Is this is too powerful? foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) { if (isset($columnDefinition['mapOnProperty'])) { $propertyName = $columnDefinition['mapOnProperty']; } else { $propertyName = Tx_Extbase_Utility_Extension::convertUnderscoredToLowerCamelCase($columnName); } // if (in_array($propertyName, $classPropertyNames)) { // TODO Enable check for property existance $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName); $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName); $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData); $dataMap->addColumnMap($columnMap); // } } // debug($dataMap); return $dataMap; }
/** * Returns an array of annotations for $propertyName on $object * * @param mixed $object * @param string $propertyName * @return array * @api */ public function getAnnotationsByProperty($object, $propertyName) { if (is_object($object)) { $className = get_class($object); } else { $className = $object; } return $this->reflectionService->getPropertyTagsValues($className, $propertyName); }
/** * Returns the type of a child object. * * @param string $parentClassName The class name of the object this proxy is part of * @param string $propertyName The name of the proxied property in it's parent * @return string The class name of the child object */ public function getType($parentClassName, $propertyName) { $propertyMetaData = $this->reflectionService->getClassSchema($parentClassName)->getProperty($propertyName); if (!empty($propertyMetaData['elementType'])) { $type = $propertyMetaData['elementType']; } elseif (!empty($propertyMetaData['type'])) { $type = $propertyMetaData['type']; } else { throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the child object type.', 1251315967); } return $type; }
/** * Ext Direct does not provide named arguments by now, so we have * to map them by reflecting on the action parameters. * * @return array The mapped arguments * @author Robert Lemke <*****@*****.**> * @author Christopher Hlubek <*****@*****.**> */ public function getArguments() { if ($this->data === array()) { return array(); } $arguments = array(); if (!$this->request->isFormPost()) { $parameters = $this->reflectionService->getMethodParameters($this->getControllerObjectName(), $this->getControllerActionName() . 'Action'); // TODO Add checks for parameters foreach ($parameters as $name => $options) { $parameterIndex = $options['position']; if (isset($this->data[$parameterIndex])) { $arguments[$name] = $this->convertObjectToArray($this->data[$parameterIndex]); } } } else { // TODO Reuse setArgumentsFromRawRequestData from Web/RequestBuilder } return $arguments; }
/** * Remove related objects * * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects * @return void */ protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) { $className = get_class($object); $dataMap = $this->dataMapper->getDataMap($className); $classSchema = $this->reflectionService->getClassSchema($className); $properties = $object->_getProperties(); foreach ($properties as $propertyName => $propertyValue) { $columnMap = $dataMap->getColumnMap($propertyName); $propertyMetaData = $classSchema->getProperty($propertyName); if ($propertyMetaData['cascade'] === 'remove') { if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) { foreach ($propertyValue as $containedObject) { $this->removeObject($containedObject); } } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) { $this->removeObject($propertyValue); } } } }
/** * Register method arguments for "render" by analysing the doc comment above. * * @return void * @author Sebastian Kurfürst <*****@*****.**> * @author Bastian Waidelich <*****@*****.**> */ private function registerRenderMethodArguments() { $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), 'render'); if (count($methodParameters) === 0) { return; } if (Tx_Fluid_Fluid::$debugMode) { $methodTags = $this->reflectionService->getMethodTagsValues(get_class($this), 'render'); $paramAnnotations = array(); if (isset($methodTags['param'])) { $paramAnnotations = $methodTags['param']; } } $i = 0; foreach ($methodParameters as $parameterName => $parameterInfo) { $dataType = NULL; if (isset($parameterInfo['type'])) { $dataType = $parameterInfo['type']; } elseif ($parameterInfo['array']) { $dataType = 'array'; } if ($dataType === NULL) { throw new Tx_Fluid_Core_Parser_Exception('could not determine type of argument "' . $parameterName . '" of the render-method in ViewHelper "' . get_class($this) . '". Either the methods docComment is invalid or some PHP optimizer strips off comments.', 1242292003); } $description = ''; if (Tx_Fluid_Fluid::$debugMode && isset($paramAnnotations[$i])) { $explodedAnnotation = explode(' ', $paramAnnotations[$i]); array_shift($explodedAnnotation); array_shift($explodedAnnotation); $description = implode(' ', $explodedAnnotation); } $defaultValue = NULL; if (isset($parameterInfo['defaultValue'])) { $defaultValue = $parameterInfo['defaultValue']; } $this->argumentDefinitions[$parameterName] = new Tx_Fluid_Core_ViewHelper_ArgumentDefinition($parameterName, $dataType, $description, $parameterInfo['optional'] === FALSE, $defaultValue, TRUE); $i++; } }
/** * Checks the request hash (HMAC), if arguments have been touched by the property mapper. * * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception. * * @return void * @throws Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash In case request hash checking failed * @author Sebastian Kurfürst <*****@*****.**> */ protected function checkRequestHash() { if (!$this->request instanceof Tx_Extbase_MVC_Web_Request) { return; } // We only want to check it for now for web requests. if ($this->request->isHmacVerified()) { return; } // all good $verificationNeeded = FALSE; foreach ($this->arguments as $argument) { if ($argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_NEWLY_CREATED || $argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED) { $verificationNeeded = TRUE; } } if ($verificationNeeded) { $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName); if (!isset($methodTagsValues['dontverifyrequesthash'])) { throw new Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash('Request hash (HMAC) checking failed. The parameter __hmac was invalid or not set, and objects were modified.', 1255082824); } } }
/** * 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. * * 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: $dataType is F3\Foo\Domain\Model\Quux, then the Validator will be found if it has the * name F3\Foo\Domain\Validator\QuuxValidator * * @param string $dataType The data type to build the validation conjunction for. Needs to be the fully qualified object name. * @return Tx_Extbase_Validation_Validator_ConjunctionValidator The validator conjunction or NULL */ protected function buildBaseValidatorConjunction($dataType) { $validatorConjunction = $this->objectManager->get('Tx_Extbase_Validation_Validator_ConjunctionValidator'); // Model based validator if (strstr($dataType, '_') !== FALSE && class_exists($dataType)) { $validatorCount = 0; $objectValidator = $this->createValidator('GenericObject'); foreach ($this->reflectionService->getClassPropertyNames($dataType) as $classPropertyName) { $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($dataType, $classPropertyName); if (!isset($classPropertyTagsValues['validate'])) { continue; } foreach ($classPropertyTagsValues['validate'] as $validateValue) { $parsedAnnotation = $this->parseValidatorAnnotation($validateValue); foreach ($parsedAnnotation['validators'] as $validatorConfiguration) { $newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']); if ($newValidator === NULL) { throw new Tx_Extbase_Validation_Exception_NoSuchValidator('Invalid validate annotation in ' . $dataType . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validatorConfiguration['validatorName'] . '".', 1241098027); } $objectValidator->addPropertyValidator($classPropertyName, $newValidator); $validatorCount++; } } } if ($validatorCount > 0) { $validatorConjunction->addValidator($objectValidator); } } // Custom validator for the class $possibleValidatorClassName = str_replace('_Model_', '_Validator_', $dataType) . 'Validator'; $customValidator = $this->createValidator($possibleValidatorClassName); if ($customValidator !== NULL) { $validatorConjunction->addValidator($customValidator); } return $validatorConjunction; }
/** * @test * @author Sebastian Kurfürst <*****@*****.**> */ public function prepareArgumentsRegistersAnnotationBasedArgumentsWithoutDescriptionIfDebugModeIsDisabled() { $this->markTestIncomplete("Works differently in v4."); Tx_Fluid_Fluid::$debugMode = FALSE; $availableClassNames = array('Tx_Fluid_Core_Fixtures_TestViewHelper'); $reflectionService = new Tx_Extbase_Reflection_Service(); $reflectionService->setStatusCache($this->getMock('Tx_Fluid_Cache_Frontend_StringFrontend', array(), array(), '', FALSE)); $reflectionService->setDataCache($this->getMock('Tx_Fluid_Cache_Frontend_VariableFrontend', array(), array(), '', FALSE)); // $reflectionService->initialize($availableClassNames); $viewHelper = new Tx_Fluid_Core_Fixtures_TestViewHelper(); $viewHelper->injectReflectionService($reflectionService); $expected = array('param1' => new Tx_Fluid_Core_ViewHelper_ArgumentDefinition('param1', 'integer', '', TRUE, null, TRUE), 'param2' => new Tx_Fluid_Core_ViewHelper_ArgumentDefinition('param2', 'array', '', TRUE, null, TRUE), 'param3' => new Tx_Fluid_Core_ViewHelper_ArgumentDefinition('param3', 'string', '', FALSE, 'default', TRUE)); $this->assertEquals($expected, $viewHelper->prepareArguments(), 'Annotation based arguments were not registered.'); }
/** * Maps the given properties to the target object WITHOUT VALIDATING THE RESULT. * If the properties could be set, this method returns TRUE, otherwise FALSE. * Returning TRUE does not mean that the target object is valid and secure! * * Only use this method if you're sure that you don't need validation! * * @param array $propertyNames Names of the properties to map. * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object. * @param object $target The target object * @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. * @return boolean TRUE if the properties could be mapped, otherwise FALSE * @see mapAndValidate() * @api */ public function map(array $propertyNames, $source, $target, $optionalPropertyNames = array()) { 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); } if (is_string($target) && strpos($target, '_') !== FALSE) { return $this->transformToObject($source, $target, '--none--'); } 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); } $this->mappingResults = new Tx_Extbase_Property_MappingResults(); if (is_object($target)) { $targetClassSchema = $this->reflectionService->getClassSchema(get_class($target)); } else { $targetClassSchema = NULL; } foreach ($propertyNames as $propertyName) { $propertyValue = NULL; if (is_array($source) || $source instanceof ArrayAccess) { if (isset($source[$propertyName])) { $propertyValue = $source[$propertyName]; } } else { $propertyValue = Tx_Extbase_Reflection_ObjectAccess::getProperty($source, $propertyName); } if ($propertyValue === NULL && !in_array($propertyName, $optionalPropertyNames)) { $this->mappingResults->addError(new Tx_Extbase_Error_Error("Required property '{$propertyName}' does not exist.", 1236785359), $propertyName); } else { if ($targetClassSchema !== NULL && $targetClassSchema->hasProperty($propertyName)) { $propertyMetaData = $targetClassSchema->getProperty($propertyName); if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage')) && (strpos($propertyMetaData['elementType'], '_') !== FALSE || $propertyValue === '')) { $objects = array(); if (is_array($propertyValue)) { foreach ($propertyValue as $value) { $objects[] = $this->transformToObject($value, $propertyMetaData['elementType'], $propertyName); } } // make sure we hand out what is expected if ($propertyMetaData['type'] === 'ArrayObject') { $propertyValue = new ArrayObject($objects); } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') { $propertyValue = new Tx_Extbase_Persistence_ObjectStorage(); foreach ($objects as $object) { $propertyValue->attach($object); } } else { $propertyValue = $objects; } } elseif ($propertyMetaData['type'] === 'DateTime' || strpos($propertyMetaData['type'], '_') !== FALSE) { $propertyValue = $this->transformToObject($propertyValue, $propertyMetaData['type'], $propertyName); if ($propertyValue === NULL) { continue; } } } elseif ($targetClassSchema !== NULL) { $this->mappingResults->addError(new Tx_Extbase_Error_Error("Property '{$propertyName}' does not exist in target class schema.", 1251813614), $propertyName); } if (is_array($target)) { $target[$propertyName] = $propertyValue; } elseif (Tx_Extbase_Reflection_ObjectAccess::setProperty($target, $propertyName, $propertyValue) === FALSE) { $this->mappingResults->addError(new Tx_Extbase_Error_Error("Property '{$propertyName}' could not be set.", 1236783102), $propertyName); } } } return !$this->mappingResults->hasErrors(); }
/** * Returns the string object class name of $property on $instanceOrClassName - or NULL if target $property is not of type object. * * @param mixed $instanceOrClassName * @param string $propertyName * @return string|NULL */ protected function assertTargetInstanceClassName($instanceOrClassName, $propertyName) { $className = TRUE === is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName; $type = $this->reflectionService->getPropertyTagValues($className, $propertyName, 'var'); $bracketPosition = strpos($type, '<'); if (FALSE !== $bracketPosition) { $type = substr($type, $bracketPosition + 1); $type = substr($type, 0, -1); } $squareBracketPosition = strpos($type, '['); if (FALSE !== $squareBracketPosition) { $type = substr($type, 0, $squareBracketPosition); } return class_exists($type) ? $type : NULL; }
/** * @test */ public function getMethodParameters() { $service = new Tx_Extbase_Reflection_Service(); $parameters = $service->getMethodParameters('Tx_Extbase_Tests_Unit_Reflection_ServiceTest', 'fixtureMethodForMethodTagsValues'); $this->assertEquals(array('foo' => array('position' => 0, 'byReference' => FALSE, 'array' => TRUE, 'optional' => FALSE, 'allowsNull' => FALSE, 'class' => NULL, 'type' => 'array')), $parameters); }
/** * Sets the data type of this argument's value * * @param string $dataType The data type. Can be either a built-in type such as "Text" or "Integer" or a fully qualified object name * @return Tx_Extbase_MVC_Controller_Argument $this * @api */ public function setDataType($dataType) { $this->dataType = $dataType; $this->dataTypeClassSchema = $this->reflectionService->getClassSchema($dataType); return $this; }
/** * Resets global singletons for the next plugin * * @return void */ protected function resetSingletons() { $this->persistenceManager->persistAll(); $this->reflectionService->shutdown(); }