/** * Makes sure that any sub classes of an adviced class also build the advices array on construction. * * @param string $className The adviced class name * @param ClassNameIndex $targetClassNameCandidates target class names for advices * @param ClassNameIndex $treatedSubClasses Already treated (sub) classes to avoid duplication * @return ClassNameIndex The new collection of already treated classes */ protected function proxySubClassesOfClassToEnsureAdvices($className, ClassNameIndex $targetClassNameCandidates, ClassNameIndex $treatedSubClasses) { if ($this->reflectionService->isClassReflected($className) === false) { return $treatedSubClasses; } if (trait_exists($className)) { return $treatedSubClasses; } if (interface_exists($className)) { return $treatedSubClasses; } $subClassNames = $this->reflectionService->getAllSubClassNamesForClass($className); foreach ($subClassNames as $subClassName) { if ($treatedSubClasses->hasClassName($subClassName)) { continue; } if ($this->reflectionService->isClassReflected($subClassName) === false || $targetClassNameCandidates->hasClassName($subClassName)) { continue; } $proxyClass = $this->compiler->getProxyClass($subClassName); if ($proxyClass === false) { continue; } $callBuildMethodsAndAdvicesArrayCode = " if (method_exists(get_parent_class(), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable('parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray')) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"; $proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); $proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); } $treatedSubClasses = $treatedSubClasses->union(new ClassNameIndex($subClassNames)); return $treatedSubClasses; }
/** * Explicitly compile proxy classes * * The compile command triggers the proxy class compilation. * Although a compilation run is triggered automatically by Flow, there might * be cases in a production context where a manual compile run is needed. * * @Flow\Internal * @param boolean $force If set, classes will be compiled even though the cache says that everything is up to date. * @return void */ public function compileCommand($force = false) { /** @var VariableFrontend $objectConfigurationCache */ $objectConfigurationCache = $this->cacheManager->getCache('Flow_Object_Configuration'); if ($force === false) { if ($objectConfigurationCache->has('allCompiledCodeUpToDate')) { return; } } /** @var PhpFrontend $classesCache */ $classesCache = $this->cacheManager->getCache('Flow_Object_Classes'); $this->proxyClassCompiler->injectClassesCache($classesCache); $this->aopProxyClassBuilder->injectObjectConfigurationCache($objectConfigurationCache); $this->aopProxyClassBuilder->build(); $this->dependencyInjectionProxyClassBuilder->build(); $classCount = $this->proxyClassCompiler->compile(); $dataTemporaryPath = $this->environment->getPathToTemporaryDirectory(); Files::createDirectoryRecursively($dataTemporaryPath); file_put_contents($dataTemporaryPath . 'AvailableProxyClasses.php', $this->proxyClassCompiler->getStoredProxyClassMap()); $objectConfigurationCache->set('allCompiledCodeUpToDate', true); $classesCacheBackend = $classesCache->getBackend(); if ($this->bootstrap->getContext()->isProduction() && $classesCacheBackend instanceof FreezableBackendInterface) { /** @var FreezableBackendInterface $backend */ $backend = $classesCache->getBackend(); $backend->freeze(); } $this->emitFinishedCompilationRun($classCount); }
/** * 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)) { 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); } } }
/** * 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); 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(['\\TYPO3\\Flow\\Object\\Proxy\\DoctrineProxyFixingTrait', '\\TYPO3\\Flow\\Aop\\AdvicesTrait']); $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; }
/** * Adds a "invokeJoinPoint()" method to the current proxy class. * * @param string $targetClassName * @return void */ protected function buildInvokeJoinPointMethodCode($targetClassName) { $proxyMethod = $this->compiler->getProxyClass($targetClassName)->getMethod('Flow_Aop_Proxy_invokeJoinPoint'); $proxyMethod->setMethodParametersCode('\\TYPO3\\Flow\\Aop\\JoinPointInterface $joinPoint'); $code = <<<'EOT' if (__CLASS__ !== $joinPoint->getClassName()) return parent::Flow_Aop_Proxy_invokeJoinPoint($joinPoint); if (isset($this->Flow_Aop_Proxy_methodIsInAdviceMode[$joinPoint->getMethodName()])) { return call_user_func_array(array('self', $joinPoint->getMethodName()), $joinPoint->getMethodArguments()); } EOT; $proxyMethod->addPreParentCallCode($code); }
/** * 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); $wakeupMethod = $proxyClass->getMethod('__wakeup'); $wakeupMethod->addPreParentCallCode($this->buildSetInstanceCode($objectConfiguration)); $wakeupMethod->addPreParentCallCode($this->buildSetRelatedEntitiesCode()); $wakeupMethod->addPostParentCallCode($this->buildLifecycleInitializationCode($objectConfiguration, \TYPO3\Flow\Object\ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED)); $wakeupMethod->addPostParentCallCode($this->buildLifecycleShutdownCode($objectConfiguration)); $sleepMethod = $proxyClass->getMethod('__sleep'); $sleepMethod->addPostParentCallCode($this->buildSerializeRelatedEntitiesCode($objectConfiguration)); $searchForEntitiesAndStoreIdentifierArrayMethod = $proxyClass->getMethod('searchForEntitiesAndStoreIdentifierArray'); $searchForEntitiesAndStoreIdentifierArrayMethod->setMethodParametersCode('$path, $propertyValue, $originalPropertyName'); $searchForEntitiesAndStoreIdentifierArrayMethod->overrideMethodVisibility('private'); $searchForEntitiesAndStoreIdentifierArrayMethod->addPreParentCallCode($this->buildSearchForEntitiesAndStoreIdentifierArrayCode()); $injectPropertiesCode = $this->buildPropertyInjectionCode($objectConfiguration); if ($injectPropertiesCode !== '') { $proxyClass->getMethod('Flow_Proxy_injectProperties')->addPreParentCallCode($injectPropertiesCode); $proxyClass->getMethod('Flow_Proxy_injectProperties')->overrideMethodVisibility('private'); $wakeupMethod->addPreParentCallCode("\t\t\$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); } } }
/** * Adds code to build the methods and advices array in case the parent class has some. * * @param string $className * @param ClassNameIndex $treatedSubClasses * @return ClassNameIndex */ protected function addBuildMethodsAndAdvicesCodeToClass($className, ClassNameIndex $treatedSubClasses) { if ($treatedSubClasses->hasClassName($className)) { return $treatedSubClasses; } $treatedSubClasses = $treatedSubClasses->union(new ClassNameIndex([$className])); if ($this->reflectionService->isClassReflected($className) === false) { return $treatedSubClasses; } $proxyClass = $this->compiler->getProxyClass($className); if ($proxyClass === false) { return $treatedSubClasses; } $callBuildMethodsAndAdvicesArrayCode = " if (method_exists(get_parent_class(), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable('parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray')) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n"; $proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); $proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode); return $treatedSubClasses; }
/** * Builds the class documentation block for the specified class keeping doc comments and vital annotations * * @return string $methodDocumentation DocComment for the given method */ protected function buildClassDocumentation() { $classDocumentation = "/**\n"; $classReflection = new \TYPO3\Flow\Reflection\ClassReflection($this->fullOriginalClassName); $classDescription = $classReflection->getDescription(); $classDocumentation .= ' * ' . str_replace("\n", "\n * ", $classDescription) . "\n"; foreach ($this->reflectionService->getClassAnnotations($this->fullOriginalClassName) as $annotation) { $classDocumentation .= ' * ' . \TYPO3\Flow\Object\Proxy\Compiler::renderAnnotation($annotation) . "\n"; } $classDocumentation .= " */\n"; return $classDocumentation; }
/** * Builds the method documentation block for the specified method keeping the vital annotations * * @param string $className Name of the class the method is declared in * @param string $methodName Name of the method to create the parameters code for * @return string $methodDocumentation DocComment for the given method */ protected function buildMethodDocumentation($className, $methodName) { $methodDocumentation = "\t/**\n\t * Autogenerated Proxy Method\n"; if ($this->reflectionService->hasMethod($className, $methodName)) { $methodTags = $this->reflectionService->getMethodTagsValues($className, $methodName); $allowedTags = array('param', 'return', 'throws'); foreach ($methodTags as $tag => $values) { if (in_array($tag, $allowedTags)) { if (count($values) === 0) { $methodDocumentation .= ' * @' . $tag . "\n"; } else { foreach ($values as $value) { $methodDocumentation .= ' * @' . $tag . ' ' . $value . "\n"; } } } } $methodAnnotations = $this->reflectionService->getMethodAnnotations($className, $methodName); foreach ($methodAnnotations as $annotation) { $methodDocumentation .= ' * ' . \TYPO3\Flow\Object\Proxy\Compiler::renderAnnotation($annotation) . "\n"; } } $methodDocumentation .= "\t */\n"; return $methodDocumentation; }
/** * @param string $classCode * @param string $expectedResult * @test * @dataProvider stripOpeningPhpTagCorrectlyStripsPhpTagDataProvider */ public function stripOpeningPhpTagCorrectlyStripsPhpTagTests($classCode, $expectedResult) { $actualResult = $this->compiler->_call('stripOpeningPhpTag', $classCode); $this->assertSame($expectedResult, $actualResult); }
/** * @dataProvider annotationsAndStrings * @test */ public function renderAnnotationRendersCorrectly($annotation, $expectedString) { $this->assertEquals($expectedString, \TYPO3\Flow\Object\Proxy\Compiler::renderAnnotation($annotation)); }