/** * 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; }
/** * Returns a FunctionDefinition from a token array. * * This method will use a set of other methods to parse a token array and retrieve any * possible information from it. This information will be entered into a FunctionDefinition object. * * @param array $tokens The token array * @param boolean $getRecursive Do we have to get the ancestral conditions as well? * * @return \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition */ protected function getDefinitionFromTokens(array $tokens, $getRecursive) { // First of all we need a new FunctionDefinition to fill $functionDefinition = new FunctionDefinition(); // For our next step we would like to get the doc comment (if any) $functionDefinition->setDocBlock($this->getDocBlock($tokens, T_FUNCTION)); // Get start and end line $functionDefinition->setStartLine($this->getStartLine($tokens)); $functionDefinition->setEndLine($this->getEndLine($tokens)); // Get the function signature $functionDefinition->setIsFinal($this->hasSignatureToken($tokens, T_FINAL, T_FUNCTION)); $functionDefinition->setIsAbstract($this->hasSignatureToken($tokens, T_ABSTRACT, T_FUNCTION)); $functionDefinition->setVisibility($this->getFunctionVisibility($tokens)); $functionDefinition->setIsStatic($this->hasSignatureToken($tokens, T_STATIC, T_FUNCTION)); $functionDefinition->setName($this->getFunctionName($tokens)); $functionDefinition->setStructureName($this->currentDefinition->getQualifiedName()); // Lets also get out parameters $functionDefinition->setParameterDefinitions($this->getParameterDefinitionList($tokens)); // Do we have a private context here? If so we have to tell the annotation parser $privateContext = false; if ($functionDefinition->getVisibility() === 'private') { $privateContext = true; } // So we got our docBlock, now we can parse the precondition annotations from it $annotationParser = new AnnotationParser($this->file, $this->config, $this->tokens, $this->currentDefinition); $functionDefinition->setPreconditions($annotationParser->getConditions($functionDefinition->getDocBlock(), Requires::ANNOTATION, $privateContext)); // get the advices $functionDefinition->getPointcutExpressions()->attach($annotationParser->getPointcutExpressions($functionDefinition->getDocBlock(), Joinpoint::TARGET_METHOD, $functionDefinition->getName())); // Does this method require the use of our "old" mechanism? $functionDefinition->setUsesOld($this->usesKeyword($functionDefinition->getDocBlock(), ReservedKeywords::OLD)); // We have to get the body of the function, so we can recreate it $functionDefinition->setBody($this->getFunctionBody($tokens)); // So we got our docBlock, now we can parse the postcondition annotations from it $functionDefinition->setPostconditions($annotationParser->getConditions($functionDefinition->getDocBlock(), Ensures::ANNOTATION, $privateContext)); // If we have to parse the definition in a recursive manner, we have to get the parent invariants if ($getRecursive === true) { // Add all the assertions we might get from ancestral dependencies $this->addAncestralAssertions($functionDefinition); } return $functionDefinition; }
/** * 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; }