Skip to content

Commit

Permalink
Function proxy dirty implementation #53
Browse files Browse the repository at this point in the history
  • Loading branch information
lisachenko committed May 23, 2013
1 parent 3d6da8c commit 3c03ee8
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/Go/Core/AspectContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Dissect\Parser\LALR1\Parser;
use Doctrine\Common\Annotations\AnnotationReader;
use TokenReflection\ReflectionClass as ParsedReflectionClass;
use TokenReflection\ReflectionFileNamespace;

/**
* Aspect container contains list of all pointcuts and advisors
Expand Down Expand Up @@ -248,6 +249,33 @@ public function getAdvicesForClass($class)
return $classAdvices;
}

/**
* Returns list of function advices for namespace
*
* @param ReflectionFileNamespace $namespace
*
* @return array
*/
public function getAdvicesForFunctions($namespace)
{
static $advices = null;

if ($namespace->getName() == 'no-namespace') {
return array();
}

if (!isset($advices)) {
$advices = array();
$functions = get_defined_functions();
foreach ($functions['internal'] as $function) {
$advices["func:{$function}"] = array(
);
}
}

return $advices;
}

/**
* Returns list of advices from advisor and point filter
*
Expand Down
11 changes: 11 additions & 0 deletions src/Go/Instrument/Transformer/WeavingTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Go\Core\AspectKernel;
use Go\Proxy\ClassProxy;

use Go\Proxy\FunctionProxy;
use Go\Proxy\TraitProxy;
use TokenReflection\Broker;
use TokenReflection\ReflectionClass as ParsedClass;
Expand Down Expand Up @@ -131,6 +132,16 @@ public function transform(StreamMetaData $metadata)
$metadata->source .= $child;
}
}

$functionAdvices = $this->container->getAdvicesForFunctions($namespace);
if ($functionAdvices) {
$functionFileName = 'functions' . $namespace->getName() . '.php';
if (!file_exists($functionFileName)) {
$source = FunctionProxy::generate($namespace, $functionAdvices);
file_put_contents($functionFileName, $source);
}
$metadata->source .= 'include_once ' . var_export($functionFileName, true) . ';'. PHP_EOL;
}
}
}

Expand Down
289 changes: 289 additions & 0 deletions src/Go/Proxy/FunctionProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
<?php
/**
* Go! OOP&AOP PHP framework
*
* @copyright Copyright 2013, Lissachenko Alexander <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/

namespace Go\Proxy;

use Go\Aop\Framework\ReflectionFunctionInvocation;
use Go\Core\AspectContainer;

use Go\Core\AspectKernel;
use ReflectionFunction;
use ReflectionParameter as Parameter;

use TokenReflection\ReflectionClass as ParsedClass;
use TokenReflection\ReflectionFileNamespace;
use TokenReflection\ReflectionParameter as ParsedParameter;
use TokenReflection\ReflectionMethod as ParsedMethod;
use TokenReflection\ReflectionFunction as ParsedFunction;

class FunctionProxy
{

/**
* List of advices for functions
*
* @var array
*/
protected static $functionAdvices = array();

/**
* Indent for source code
*
* @var int
*/
protected $indent = 4;

/**
* Name for the current namespace
*
* @var string
*/
protected $namespace = '';

/**
* Source code for functions
*
* @var array Name of the function => source code for it
*/
protected $functionsCode = array();

/**
* List of advices that are used for generation of stubs
*
* @var array
*/
protected $advices = array();

/**
* Constructs functions stub class from namespace Reflection
*
* @param ReflectionFileNamespace $namespace Reflection of namespace
* @param array $advices List of function advices
*
* @throws \InvalidArgumentException for invalid classes
*/
protected function __construct($namespace, array $advices = array())
{
if (!$namespace instanceof ReflectionFileNamespace) {
throw new \InvalidArgumentException("Invalid argument for namespace");
}
$this->advices = $advices;
$this->namespace = $namespace;
}

/**
* Generates an child code by parent class reflection and joinpoints for it
*
* @param ReflectionFileNamespace $namespace Reflection of namespace
* @param array|Advice[] $advices List of function advices
*
* @throws \InvalidArgumentException for unsupported advice type
* @return ClassProxy
*/
public static function generate($namespace, array $advices)
{
$functions = new self($namespace, $advices);
if (!empty($advices)) {
foreach ($advices as $name => $value) {

list ($type, $pointName) = explode(':', $name, 2);
switch ($type) {
case 'func':
$function = new ReflectionFunction($pointName);
$functions->override($function, $functions->getJoinpointInvocationBody($function));
break;

default:
throw new \InvalidArgumentException("Unsupported point `$type`");
}
}
}
return $functions;
}

public static function getJoinPoint($functionName, $namespace)
{
$advices = self::$functionAdvices[$namespace][$functionName];
return new ReflectionFunctionInvocation($functionName, $advices);
}


/**
* Inject advices for given trait
*
* NB This method will be used as a callback during source code evaluation to inject joinpoints
*
* @param string $namespace Aop child proxy class
* @param array|Advice[] $advices List of advices to inject into class
*
* @return void
*/
public static function injectJoinPoints($namespace, array $advices = array())
{
if (!$advices) {
$container = AspectKernel::getInstance()->getContainer();
$advices = $container->getAdvicesForFunctions($namespace);
}
self::$functionAdvices[$namespace] = $advices;
}

/**
* Override function with new body
*
* @param ReflectionFunction|ParsedFunction $function Function reflection
* @param string $body New body for function
*
* @return AbstractProxy
*/
public function override($function, $body)
{
$this->functionsCode[$function->name] = $this->getOverriddenFunction($function, $body);
return $this;
}

/**
* {@inheritDoc}
*/
public function __toString()
{
$serialized = serialize($this->advices);
ksort($this->functionsCode);

$functionsCode = sprintf("<?php\n%s\nnamespace %s;\n%s",
$this->namespace->getDocComment(),
$this->namespace->getName(),
join("\n", $this->functionsCode)
);

return $functionsCode
// Inject advices on call
. PHP_EOL
. '\\' . __CLASS__ . "::injectJoinPoints('"
. $this->namespace->getName() . "',"
. " \unserialize(" . var_export($serialized, true) . "));";
}

/**
* Creates a function code from Reflection
*
* @param ParsedFunction $function Reflection for function
* @param string $body Body of function
*
* @return string
*/
protected function getOverriddenFunction($function, $body)
{
$code = sprintf("%sfunction %s%s(%s)\n{\n%s\n}\n",
preg_replace('/ {4}|\t/', '', $function->getDocComment()) ."\n",
$function->returnsReference() ? '&' : '',
$function->getName(),
join(', ', $this->getParameters($function->getParameters())),
$this->indent($body)
);
return $code;
}

/**
* Creates definition for trait method body
*
* @param ReflectionFunction|ParsedFunction $function Method reflection
*
* @return string new method body
*/
protected function getJoinpointInvocationBody($function)
{
$class = '\\' . __CLASS__;
$prefix = 'func';

$args = join(', ', array_map(function ($param) {
/** @var $param Parameter|ParsedParameter */
$byReference = $param->isPassedByReference() ? '&' : '';
return $byReference . '$' . $param->name;
}, $function->getParameters()));

$args = strpos($args, '...') === false ? "array($args)" : 'func_get_args()';
return <<<BODY
static \$__joinPoint = null;
if (!\$__joinPoint) {
\$__joinPoint = {$class}::getJoinPoint('{$prefix}:{$function->name}', __NAMESPACE__);
}
return \$__joinPoint->__invoke($args);
BODY;
}


/**
* Indent block of code
*
* @param string $text Non-indented text
*
* @return string Indented text
*/
protected function indent($text)
{
$pad = str_pad('', $this->indent, ' ');
$lines = array_map(function ($line) use ($pad) {
return $pad . $line;
}, explode("\n", $text));
return join("\n", $lines);
}

/**
* Returns list of string representation of parameters
*
* @param array|Parameter[]|ParsedParameter[] $parameters List of parameters
*
* @return array
*/
protected function getParameters(array $parameters)
{
$parameterDefinitions = array();
foreach ($parameters as $parameter) {
if ($parameter->name == '...') {
continue;
}
$parameterDefinitions[] = $this->getParameterCode($parameter);
}
return $parameterDefinitions;
}

/**
* Return string representation of parameter
*
* @param Parameter|ParsedParameter $parameter Reflection parameter
*
* @return string
*/
protected function getParameterCode($parameter)
{
$type = '';
if ($parameter->isArray()) {
$type = 'array';
} elseif ($parameter->getClass()) {
$type = '\\' . $parameter->getClass()->name;
}
$defaultValue = null;
$isDefaultValueAvailable = $parameter->isDefaultValueAvailable();
if ($isDefaultValueAvailable) {
if ($parameter instanceof ParsedParameter) {
$defaultValue = $parameter->getDefaultValueDefinition();
} else {
$defaultValue = var_export($parameter->getDefaultValue());
}
} elseif ($parameter->allowsNull()) {
$defaultValue = 'null';
}
$code = sprintf('%s%s$%s%s',
$type ? "$type " : '',
$parameter->isPassedByReference() ? '&' : '',
$parameter->name,
$isDefaultValueAvailable ? (" = " . $defaultValue) : ''
);
return $code;
}
}

0 comments on commit 3c03ee8

Please sign in to comment.