/** * Traverses through the given class and interface names and builds a base object configuration * for all of them. Then parses the provided extra configuration and merges the result * into the overall configuration. Finally autowires dependencies of arguments and properties * which can be resolved automatically. * * @param array $availableClassNamesByPackage An array of available class names, grouped by package key * @param array $rawObjectConfigurationsByPackages An array of package keys and their raw (ie. unparsed) object configurations * @return array<TYPO3\Flow\Object\Configuration\Configuration> Object configurations * @throws \TYPO3\Flow\Object\Exception\InvalidObjectConfigurationException */ public function buildObjectConfigurations(array $availableClassNamesByPackage, array $rawObjectConfigurationsByPackages) { $objectConfigurations = array(); foreach ($availableClassNamesByPackage as $packageKey => $classNames) { foreach ($classNames as $className) { $objectName = $className; if ($this->reflectionService->isClassUnconfigurable($className)) { continue; } if ($this->reflectionService->isClassFinal($className)) { continue; } if (interface_exists($className)) { $interfaceName = $className; $className = $this->reflectionService->getDefaultImplementationClassNameForInterface($interfaceName); if ($className === FALSE) { continue; } if ($this->reflectionService->isClassAnnotatedWith($interfaceName, 'TYPO3\\Flow\\Annotations\\Scope')) { throw new \TYPO3\Flow\Object\Exception\InvalidObjectConfigurationException(sprintf('Scope annotations in interfaces don\'t have any effect, therefore you better remove it from %s in order to avoid confusion.', $interfaceName), 1299095595); } } $rawObjectConfiguration = array('className' => $className); $rawObjectConfiguration = $this->enhanceRawConfigurationWithAnnotationOptions($className, $rawObjectConfiguration); $objectConfigurations[$objectName] = $this->parseConfigurationArray($objectName, $rawObjectConfiguration, 'automatically registered class'); $objectConfigurations[$objectName]->setPackageKey($packageKey); } } foreach ($rawObjectConfigurationsByPackages as $packageKey => $rawObjectConfigurations) { foreach ($rawObjectConfigurations as $objectName => $rawObjectConfiguration) { $objectName = str_replace('_', '\\', $objectName); if (!is_array($rawObjectConfiguration)) { throw new \TYPO3\Flow\Object\Exception\InvalidObjectConfigurationException('Configuration of object "' . $objectName . '" in package "' . $packageKey . '" is not an array, please check your Objects.yaml for syntax errors.', 1295954338); } $existingObjectConfiguration = isset($objectConfigurations[$objectName]) ? $objectConfigurations[$objectName] : NULL; if (isset($rawObjectConfiguration['className'])) { $rawObjectConfiguration = $this->enhanceRawConfigurationWithAnnotationOptions($rawObjectConfiguration['className'], $rawObjectConfiguration); } $newObjectConfiguration = $this->parseConfigurationArray($objectName, $rawObjectConfiguration, 'configuration of package ' . $packageKey . ', definition for object "' . $objectName . '"', $existingObjectConfiguration); if (!isset($objectConfigurations[$objectName]) && !interface_exists($objectName, TRUE) && !class_exists($objectName, FALSE)) { throw new \TYPO3\Flow\Object\Exception\InvalidObjectConfigurationException('Tried to configure unknown object "' . $objectName . '" in package "' . $packageKey . '". Please check your Objects.yaml.', 1184926175); } if ($objectName !== $newObjectConfiguration->getClassName() && !interface_exists($objectName, TRUE)) { throw new \TYPO3\Flow\Object\Exception\InvalidObjectConfigurationException('Tried to set a differing class name for class "' . $objectName . '" in the object configuration of package "' . $packageKey . '". Setting "className" is only allowed for interfaces, please check your Objects.yaml."', 1295954589); } $objectConfigurations[$objectName] = $newObjectConfiguration; if ($objectConfigurations[$objectName]->getPackageKey() === NULL) { $objectConfigurations[$objectName]->setPackageKey($packageKey); } } } $this->autowireArguments($objectConfigurations); $this->autowireProperties($objectConfigurations); return $objectConfigurations; }
/** * Analyzes the Object Configuration provided by the compiler and builds the necessary PHP code for the proxy classes * to realize dependency injection. * * @return void */ public function build() { $this->objectConfigurations = $this->objectManager->getObjectConfigurations(); foreach ($this->objectConfigurations as $objectName => $objectConfiguration) { $className = $objectConfiguration->getClassName(); if ($className === '' || $this->compiler->hasCacheEntryForClass($className) === true) { continue; } if ($objectName !== $className || $this->reflectionService->isClassAbstract($className) || $this->reflectionService->isClassFinal($className)) { continue; } $proxyClass = $this->compiler->getProxyClass($className); if ($proxyClass === false) { continue; } $this->systemLogger->log('Building DI proxy for "' . $className . '".', LOG_DEBUG); $constructorPreCode = ''; $constructorPostCode = ''; $constructorPreCode .= $this->buildSetInstanceCode($objectConfiguration); $constructorPreCode .= $this->buildConstructorInjectionCode($objectConfiguration); $setRelatedEntitiesCode = ''; if (!$this->reflectionService->hasMethod($className, '__sleep')) { $proxyClass->addTraits(['\\TYPO3\\Flow\\Object\\Proxy\\ObjectSerializationTrait']); $sleepMethod = $proxyClass->getMethod('__sleep'); $sleepMethod->addPostParentCallCode($this->buildSerializeRelatedEntitiesCode($objectConfiguration)); $setRelatedEntitiesCode = "\n " . '$this->Flow_setRelatedEntities();' . "\n"; } $wakeupMethod = $proxyClass->getMethod('__wakeup'); $wakeupMethod->addPreParentCallCode($this->buildSetInstanceCode($objectConfiguration)); $wakeupMethod->addPreParentCallCode($setRelatedEntitiesCode); $wakeupMethod->addPostParentCallCode($this->buildLifecycleInitializationCode($objectConfiguration, \TYPO3\Flow\Object\ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED)); $wakeupMethod->addPostParentCallCode($this->buildLifecycleShutdownCode($objectConfiguration)); $injectPropertiesCode = $this->buildPropertyInjectionCode($objectConfiguration); if ($injectPropertiesCode !== '') { $proxyClass->addTraits(['\\TYPO3\\Flow\\Object\\DependencyInjection\\PropertyInjectionTrait']); $proxyClass->getMethod('Flow_Proxy_injectProperties')->addPreParentCallCode($injectPropertiesCode); $proxyClass->getMethod('Flow_Proxy_injectProperties')->overrideMethodVisibility('private'); $wakeupMethod->addPreParentCallCode(" \$this->Flow_Proxy_injectProperties();\n"); $constructorPostCode .= ' if (\'' . $className . '\' === get_class($this)) {' . "\n"; $constructorPostCode .= ' $this->Flow_Proxy_injectProperties();' . "\n"; $constructorPostCode .= ' }' . "\n"; } $constructorPostCode .= $this->buildLifecycleInitializationCode($objectConfiguration, \TYPO3\Flow\Object\ObjectManagerInterface::INITIALIZATIONCAUSE_CREATED); $constructorPostCode .= $this->buildLifecycleShutdownCode($objectConfiguration); $constructor = $proxyClass->getConstructor(); $constructor->addPreParentCallCode($constructorPreCode); $constructor->addPostParentCallCode($constructorPostCode); if ($this->objectManager->getContext()->isProduction()) { $this->compileStaticMethods($className, $proxyClass); } } }
/** * Determines which of the given classes are potentially proxyable * and returns their names in an array. * * @param array $classNamesByPackage Names of the classes to check * @return array Names of classes which can be proxied */ protected function getProxyableClasses(array $classNamesByPackage) { $proxyableClasses = array(); foreach ($classNamesByPackage as $classNames) { foreach ($classNames as $className) { if (!in_array(substr($className, 0, 15), $this->blacklistedSubPackages)) { if (!$this->reflectionService->isClassAnnotatedWith($className, 'TYPO3\\Flow\\Annotations\\Aspect') && !$this->reflectionService->isClassFinal($className)) { $proxyableClasses[] = $className; } } } } return $proxyableClasses; }
/** * Renders and returns the PHP code for this ProxyClass. * * @return string */ public function render() { $namespace = $this->namespace; $proxyClassName = $this->originalClassName; $originalClassName = $this->originalClassName . \TYPO3\Flow\Object\Proxy\Compiler::ORIGINAL_CLASSNAME_SUFFIX; $classModifier = ''; if ($this->reflectionService->isClassAbstract($this->fullOriginalClassName)) { $classModifier = 'abstract '; } elseif ($this->reflectionService->isClassFinal($this->fullOriginalClassName)) { $classModifier = 'final '; } $constantsCode = $this->renderConstantsCode(); $propertiesCode = $this->renderPropertiesCode(); $traitsCode = $this->renderTraitsCode(); $methodsCode = isset($this->constructor) ? $this->constructor->render() : ''; foreach ($this->methods as $proxyMethod) { $methodsCode .= $proxyMethod->render(); } if ($methodsCode . $constantsCode === '') { return ''; } $classCode = ($namespace !== '' ? 'namespace ' . $namespace . ";\n\n" : '') . "use Doctrine\\ORM\\Mapping as ORM;\n" . "use TYPO3\\Flow\\Annotations as Flow;\n" . "\n" . $this->buildClassDocumentation() . $classModifier . 'class ' . $proxyClassName . ' extends ' . $originalClassName . ' implements ' . implode(', ', array_unique($this->interfaces)) . " {\n\n" . $traitsCode . $constantsCode . $propertiesCode . $methodsCode . '}'; return $classCode; }
/** * Traverses through the given class and interface names and builds a base object configuration * for all of them. Then parses the provided extra configuration and merges the result * into the overall configuration. Finally autowires dependencies of arguments and properties * which can be resolved automatically. * * @param array $availableClassAndInterfaceNamesByPackage An array of available class names, grouped by package key * @param array $rawObjectConfigurationsByPackages An array of package keys and their raw (ie. unparsed) object configurations * @return array<TYPO3\Flow\Object\Configuration\Configuration> Object configurations * @throws InvalidObjectConfigurationException */ public function buildObjectConfigurations(array $availableClassAndInterfaceNamesByPackage, array $rawObjectConfigurationsByPackages) { $objectConfigurations = array(); $interfaceNames = array(); foreach ($availableClassAndInterfaceNamesByPackage as $packageKey => $classAndInterfaceNames) { foreach ($classAndInterfaceNames as $classOrInterfaceName) { $objectName = $classOrInterfaceName; if ($this->reflectionService->isClassUnconfigurable($classOrInterfaceName)) { continue; } if ($this->reflectionService->isClassFinal($classOrInterfaceName)) { continue; } if (interface_exists($classOrInterfaceName)) { $interfaceName = $classOrInterfaceName; $implementationClassName = $this->reflectionService->getDefaultImplementationClassNameForInterface($interfaceName); if (!isset($rawObjectConfigurationsByPackages[$packageKey][$interfaceName]) && $implementationClassName === FALSE) { continue; } if ($this->reflectionService->isClassAnnotatedWith($interfaceName, \TYPO3\Flow\Annotations\Scope::class)) { throw new InvalidObjectConfigurationException(sprintf('Scope annotations in interfaces don\'t have any effect, therefore you better remove it from %s in order to avoid confusion.', $interfaceName), 1299095595); } $interfaceNames[$interfaceName] = TRUE; } else { $implementationClassName = $classOrInterfaceName; } $rawObjectConfiguration = array('className' => $implementationClassName); $rawObjectConfiguration = $this->enhanceRawConfigurationWithAnnotationOptions($classOrInterfaceName, $rawObjectConfiguration); $objectConfigurations[$objectName] = $this->parseConfigurationArray($objectName, $rawObjectConfiguration, 'automatically registered class'); $objectConfigurations[$objectName]->setPackageKey($packageKey); } } foreach ($rawObjectConfigurationsByPackages as $packageKey => $rawObjectConfigurations) { foreach ($rawObjectConfigurations as $objectName => $rawObjectConfiguration) { $objectName = str_replace('_', '\\', $objectName); if (!is_array($rawObjectConfiguration)) { throw new InvalidObjectConfigurationException('Configuration of object "' . $objectName . '" in package "' . $packageKey . '" is not an array, please check your Objects.yaml for syntax errors.', 1295954338); } $existingObjectConfiguration = isset($objectConfigurations[$objectName]) ? $objectConfigurations[$objectName] : NULL; if (isset($rawObjectConfiguration['className'])) { $rawObjectConfiguration = $this->enhanceRawConfigurationWithAnnotationOptions($rawObjectConfiguration['className'], $rawObjectConfiguration); } $newObjectConfiguration = $this->parseConfigurationArray($objectName, $rawObjectConfiguration, 'configuration of package ' . $packageKey . ', definition for object "' . $objectName . '"', $existingObjectConfiguration); if (!isset($objectConfigurations[$objectName]) && !interface_exists($objectName, TRUE) && !class_exists($objectName, FALSE)) { throw new InvalidObjectConfigurationException('Tried to configure unknown object "' . $objectName . '" in package "' . $packageKey . '". Please check your Objects.yaml.', 1184926175); } if ($objectName !== $newObjectConfiguration->getClassName() && !interface_exists($objectName, TRUE)) { throw new InvalidObjectConfigurationException('Tried to set a differing class name for class "' . $objectName . '" in the object configuration of package "' . $packageKey . '". Setting "className" is only allowed for interfaces, please check your Objects.yaml."', 1295954589); } if (empty($newObjectConfiguration->getClassName()) && empty($newObjectConfiguration->getFactoryObjectName())) { $count = count($this->reflectionService->getAllImplementationClassNamesForInterface($objectName)); $hint = $count ? 'It seems like there is no class which implements that interface, maybe the object configuration is obsolete?' : sprintf('There are %s classes implementing that interface, therefore you must specify a specific class in your object configuration.', $count); throw new InvalidObjectConfigurationException('The object configuration for "' . $objectName . '" in the object configuration of package "' . $packageKey . '" lacks a "className" entry. ' . $hint, 1422566751); } $objectConfigurations[$objectName] = $newObjectConfiguration; if ($objectConfigurations[$objectName]->getPackageKey() === NULL) { $objectConfigurations[$objectName]->setPackageKey($packageKey); } } } // If an implementation class could be determined for an interface object configuration, set the scope for the // interface object configuration to the scope found in the implementation class configuration: foreach (array_keys($interfaceNames) as $interfaceName) { $implementationClassName = $objectConfigurations[$interfaceName]->getClassName(); if ($implementationClassName !== '' && isset($objectConfigurations[$implementationClassName])) { $objectConfigurations[$interfaceName]->setScope($objectConfigurations[$implementationClassName]->getScope()); } } $this->autowireArguments($objectConfigurations); $this->autowireProperties($objectConfigurations); return $objectConfigurations; }