/** * This method will add all assertions any ancestral structures (parent classes, implemented interfaces) might have * to the passed class definition. * * @param \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition $functionDefinition The function definition * we are working on * * @return void */ protected function addAncestralAssertions(FunctionDefinition $functionDefinition) { $dependencies = $this->currentDefinition->getDependencies(); foreach ($dependencies as $dependency) { // freshly set the dependency definition to avoid side effects $dependencyDefinition = null; $fileEntry = $this->structureMap->getEntry($dependency); if (!$fileEntry instanceof Structure) { // Continue, don't fail as we might have dependencies which are not under Doppelgaenger surveillance continue; } // Get the needed parser $structureParserFactory = new StructureParserFactory(); $parser = $structureParserFactory->getInstance($fileEntry->getType(), $fileEntry->getPath(), $this->config, $this->structureMap, $this->structureDefinitionHierarchy); // Get the definition $dependencyDefinition = $parser->getDefinition($dependency, true); // Get the function definitions of the dependency structure $dependencyFunctionDefinitions = $dependencyDefinition->getFunctionDefinitions(); // If we have a method with the name of the current one we have to get the conditions as ancestrals if ($dependencyFunctionDefinitions->entryExists($functionDefinition->getName())) { // Get the definition $dependencyFunctionDefinition = $dependencyFunctionDefinitions->get($functionDefinition->getName()); // If the ancestral function uses the old keyword we have to do too if ($dependencyFunctionDefinition->usesOld() !== false) { $functionDefinition->setUsesOld(true); } // Get the conditions $functionDefinition->setAncestralPreconditions($dependencyFunctionDefinition->getAllPreconditions(true)); $functionDefinition->setAncestralPostconditions($dependencyFunctionDefinition->getAllPostconditions(true)); } } }
/** * Will inject enforcement processing for a certain function. * Will take default processing code into account and check for custom processing configurations * * @param string $bucketData Payload of the currently filtered bucket * @param string $structureName The name of the structure for which we create the enforcement code * @param string $structurePath Path to the file containing the structure * @param string $preconditionCode Default precondition processing code * @param string $postconditionCode Default post-condition processing code * @param FunctionDefinition $functionDefinition Function definition to create the code for * * @return null */ protected function injectFunctionEnforcement(&$bucketData, $structureName, $structurePath, $preconditionCode, $postconditionCode, FunctionDefinition $functionDefinition) { $functionName = $functionDefinition->getName(); // try to find a local enforcement processing configuration, if we find something we have to // create new enforcement code based on that information $localType = $this->filterLocalProcessing($functionDefinition->getDocBlock()); if ($localType !== false) { // we found something, make a backup of default enforcement and generate the new code $preconditionCode = $this->generateCode($structureName, 'precondition', $localType, $structurePath); $postconditionCode = $this->generateCode($structureName, 'postcondition', $localType, $structurePath); } // Insert the code for the static processing placeholders $bucketData = str_replace(array(Placeholders::ENFORCEMENT . $functionName . 'precondition' . Placeholders::PLACEHOLDER_CLOSE, Placeholders::ENFORCEMENT . $functionName . 'postcondition' . Placeholders::PLACEHOLDER_CLOSE), array($preconditionCode, $postconditionCode), $bucketData); }
/** * Will change code to create an entry for the old object state. * * @param string $bucketData Payload of the currently * filtered bucket * @param \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition $functionDefinition Currently handled function * * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException * * @return boolean */ protected function injectOldCode(&$bucketData, FunctionDefinition &$functionDefinition) { // Do we even need to do anything? if ($functionDefinition->usesOld() !== true) { return false; } // If the function is static it should not use the dgOld keyword as there is no state to the class! if ($functionDefinition->isStatic() === true) { throw new GeneratorException(sprintf('Cannot clone class state in static method %s.', $functionDefinition->getName())); } // Still here? Then inject the clone statement to preserve an instance of the object prior to our call. $bucketData = str_replace(Placeholders::OLD_SETUP . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE, ReservedKeywords::OLD . ' = clone $this;', $bucketData); // Still here? We encountered no error then. return true; }
/** * Will generate the skeleton code for the passed function definition. * Will result in a string resembling the following example: * * <FUNCTION_DOCBLOCK> * <FUNCTION_MODIFIERS> function <FUNCTION_NAME>(<FUNCTION_PARAMS>) * { * $dgStartLine = <FUNCTION_START_LINE>; * $dgEndLine = <FUNCTION_END_LINE>; * / DOPPELGAENGER_FUNCTION_BEGIN_PLACEHOLDER <FUNCTION_NAME> / * / DOPPELGAENGER_BEFORE_JOINPOINT <FUNCTION_NAME> / * $dgOngoingContract = \AppserverIo\Doppelgaenger\ContractContext::open(); * / DOPPELGAENGER_INVARIANT_PLACEHOLDER / * / DOPPELGAENGER_PRECONDITION_PLACEHOLDER <FUNCTION_NAME> / * / DOPPELGAENGER_OLD_SETUP_PLACEHOLDER <FUNCTION_NAME> / * $dgResult = null; * try { * / DOPPELGAENGER_AROUND_JOINPOINT <FUNCTION_NAME> / * * } catch (\Exception $dgThrownExceptionObject) { * / DOPPELGAENGER_AFTERTHROWING_JOINPOINT <FUNCTION_NAME> / * * // rethrow the exception * throw $dgThrownExceptionObject; * * } finally { * / DOPPELGAENGER_AFTER_JOINPOINT <FUNCTION_NAME> / * * } * / DOPPELGAENGER_POSTCONDITION_PLACEHOLDER <FUNCTION_NAME> / * / DOPPELGAENGER_INVARIANT_PLACEHOLDER / * if ($dgOngoingContract) { * \AppserverIo\Doppelgaenger\ContractContext::close(); * } / DOPPELGAENGER_AFTERRETURNING_JOINPOINT <FUNCTION_NAME> / * * return $dgResult; * } * * @param boolean $injectNeeded Determine if we have to use a try...catch block * @param FunctionDefinition $functionDefinition The function definition object * * @return string */ protected function generateSkeletonCode($injectNeeded, FunctionDefinition $functionDefinition) { // first of all: the docblock $code = ' ' . $functionDefinition->getDocBlock() . ' ' . $functionDefinition->getHeader('definition') . ' { ' . ReservedKeywords::START_LINE_VARIABLE . ' = ' . (int) $functionDefinition->getStartLine() . '; ' . ReservedKeywords::END_LINE_VARIABLE . ' = ' . (int) $functionDefinition->getEndLine() . '; ' . Placeholders::FUNCTION_BEGIN . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' '; // right after: the "before" join-point $code .= Placeholders::BEFORE_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' '; // open the contract context so we are able to avoid endless recursion $code .= ReservedKeywords::CONTRACT_CONTEXT . ' = \\AppserverIo\\Doppelgaenger\\ContractContext::open(); '; // Invariant is not needed in private or static functions. // Also make sure that there is none in front of the constructor check if ($functionDefinition->getVisibility() !== 'private' && !$functionDefinition->isStatic() && $functionDefinition->getName() !== '__construct') { $code .= Placeholders::INVARIANT_CALL_START . ' '; } $code .= Placeholders::PRECONDITION . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' ' . Placeholders::OLD_SETUP . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' '; // we will wrap code execution in order to provide a "finally" and "after throwing" placeholder hook. // we will also predefine the result as NULL to avoid warnings $code .= ReservedKeywords::RESULT . ' = null; try { ' . Placeholders::AROUND_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' '; // add the second part of the try/catch/finally block $code .= ' } catch (\\Exception ' . ReservedKeywords::THROWN_EXCEPTION_OBJECT . ') { ' . Placeholders::AFTERTHROWING_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' // rethrow the exception throw ' . ReservedKeywords::THROWN_EXCEPTION_OBJECT . '; } finally { '; // if we have to inject additional code, we might do so here if ($injectNeeded === true) { $code .= Placeholders::METHOD_INJECT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE; } // finish of the block $code .= ' ' . Placeholders::AFTER_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' } '; // now just place all the other placeholder for other filters to come $code .= ' ' . Placeholders::POSTCONDITION . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE; // Invariant is not needed in private or static functions if ($functionDefinition->getVisibility() !== 'private' && !$functionDefinition->isStatic()) { $code .= ' ' . Placeholders::INVARIANT_CALL_END . ' '; } // close of the contract context $code .= 'if (' . ReservedKeywords::CONTRACT_CONTEXT . ') { \\AppserverIo\\Doppelgaenger\\ContractContext::close(); } '; // last of all: the "after returning" join-point and the final return from the proxy $code .= ' ' . Placeholders::AFTERRETURNING_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' return ' . ReservedKeywords::RESULT . '; } '; return $code; }
/** * Will inject invocation code for a given function into a given piece of code. * Invocation code will be the instantiation of a \AppserverIo\Doppelgaenger\Entities\MethodInvocation object * as a basic representation of the given function * * @param string $bucketData Reference on the current bucket's data * @param FunctionDefinition $functionDefinition Definition of the function to inject invocation code into * @param array $callbackChain Chain of callbacks which is used to recursively chain calls * * @return boolean */ protected function injectInvocationCode(&$bucketData, FunctionDefinition $functionDefinition, array $callbackChain) { // start building up the code $code = ' ' . ReservedKeywords::METHOD_INVOCATION_OBJECT . ' = new \\AppserverIo\\Doppelgaenger\\Entities\\MethodInvocation( '; // add the original method call to the callback chain so it can be integrated, add it and add the context if ($functionDefinition->isStatic()) { $contextCode = '__CLASS__'; } else { $contextCode = '$this'; } // iterate the callback chain and build up the code but pop the first element as we will invoke it initially unset($callbackChain[0]); // empty chain? Add the original function at least if (empty($callbackChain)) { $callbackChain[] = array($functionDefinition->getStructureName(), $functionDefinition->getName()); } $code .= ' array('; foreach ($callbackChain as $callback) { // do some brushing up for the structure $structure = $callback[0]; if ($structure === $functionDefinition->getStructureName()) { $structure = $contextCode; } // also brush up the function call to direct to the original if ($callback[1] === $functionDefinition->getName()) { $callback[1] = $functionDefinition->getName() . ReservedKeywords::ORIGINAL_FUNCTION_SUFFIX; } $code .= 'array(' . $structure . ', \'' . $callback[1] . '\'),'; } $code .= '), '; // continue with the access modifiers $code .= ' ' . $contextCode . ', ' . ($functionDefinition->isAbstract() ? 'true' : 'false') . ', ' . ($functionDefinition->isFinal() ? 'true' : 'false') . ', ' . ($functionDefinition->isStatic() ? 'true' : 'false') . ', '; // we have to build up manual parameter collection as func_get_args() only returns copies // @see http://php.net/manual/en/function.func-get-args.php $parametersCode = ' array('; foreach ($functionDefinition->getParameterDefinitions() as $parameterDefinition) { $name = $parameterDefinition->name; $parametersCode .= '\'' . substr($name, 1) . '\' => ' . $name . ','; } $parametersCode .= ')'; $code .= ' \'' . $functionDefinition->getName() . '\', ' . $parametersCode . ', __CLASS__, \'' . $functionDefinition->getVisibility() . '\' );'; // Insert the code $placeholder = Placeholders::FUNCTION_BEGIN . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE; $bucketData = str_replace($placeholder, $placeholder . $code, $bucketData); return true; }
/** * Used to "straighten out" an expression as some expressions allow for shell regex which makes them hard to * generate code from. * So with this method a matching pointcut can be altered into having a directly readable expression * * @param FunctionDefinition|AttributeDefinition $definition Definition to straighten the expression against * * @return null */ public function straightenExpression($definition) { // structure name has to be absolute $structureName = '\\' . ltrim($definition->getStructureName(), '\\'); // fix the expression $this->expression = str_replace(array($this->callType . $this->function, $this->structure), array($this->callType . $definition->getName(), $structureName), $this->getExpression()); // set the obvious properties $this->function = $definition->getName(); $this->structure = $structureName; }