Last active
March 20, 2019 16:55
-
-
Save kanian/cfa9f2ac4a1db73e7ef0c6b2791a7075 to your computer and use it in GitHub Desktop.
Builds a Proxy to a Subject Class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Assoa\Patterns\Proxy; | |
use Assoa\Parser\INodeVisitorFactory; | |
use Assoa\Parser\ProxyNodeVisitorFactory; | |
use Assoa\Patterns\IPattern; | |
use Assoa\Patterns\Pattern; | |
use InvalidArgumentException; | |
use PhpParser\NodeTraverser; | |
use PhpParser\Node\Expr\Assign; | |
use PhpParser\Node\Stmt\ClassMethod; | |
use PhpParser\Node\Stmt\Expression; | |
class Proxy extends Pattern implements IPattern | |
{ | |
protected $subjectClass; | |
protected $subjectClassShortName; | |
protected $subjectClassAst; | |
protected $factory; | |
protected $patternClassShortName; | |
protected $patternAstBuilder; | |
protected $patternClassAstBuilder; | |
protected $methodAsts = []; | |
/** | |
* Constructs the proxy class as a subclass of the subject class | |
* | |
* @param INodeVisitorFactory $nodeVisitor instance | |
* @throws InvalidArgumentException | |
*/ | |
public function __construct(INodeVisitorFactory $nodeVisitor) | |
{ | |
parent::__construct($nodeVisitor); | |
} | |
public function patternize() | |
{ | |
$args = func_get_args(); | |
parent::patternize(...$args); | |
$this->proxify(); | |
} | |
/** | |
* Builds the AST of the proxy class | |
* | |
* @return void | |
*/ | |
protected function proxify() | |
{ | |
$classFileContents = $this->getSubjectClassFileContents(); | |
$this->subjectClassAst = $this->astParser->parse($classFileContents); | |
$traverser = new NodeTraverser; | |
$traverser->addVisitor( | |
ProxyNodeVisitorFactory::get( | |
$this->subjectClassShortName, $this->getBuilderCommands() | |
) | |
); | |
$traverser->traverse($this->subjectClassAst); | |
$this->ideatePattern(); | |
} | |
public function buildClassDeclaration() | |
{ | |
$this->patternClassAstBuilder = $this->factory->class($this->patternClassShortName) | |
->extend($this->subjectClassShortName); | |
for ($i = 0; $i < count($this->methodAsts); $i++) { | |
$this->patternClassAstBuilder = $this->patternClassAstBuilder->addStmt($this->methodAsts[$i]); | |
} | |
for ($i = 0; $i < count($this->propertyAsts); $i++) { | |
$this->patternClassAstBuilder = $this->patternClassAstBuilder->addStmt($this->propertyAsts[$i]); | |
} | |
if (empty($this->patternAstBuilder)) { | |
$this->patternAstBuilder = $this->patternClassAstBuilder; | |
} else { | |
$this->patternAstBuilder = $this->patternAstBuilder->addStmt($this->patternClassAstBuilder); | |
} | |
} | |
public function buildMethodDeclaration(ClassMethod $method) | |
{ | |
$name = $method->name; | |
$methodAstBuilder = $this->methodDeclarationPrelude($method); | |
//Forward calls to subject instance | |
$methodCallNode = $this->factory->methodCall( | |
$this->factory->propertyFetch( | |
$this->factory->var('this'), | |
$this->factory->val('subjectInstance') | |
), | |
$name->name, | |
array(new Arg(new Variable("args"), false, true)) | |
); | |
//Call return | |
$methodReturnNode = new Return_( | |
$methodCallNode | |
); | |
$methodAstBuilder = $methodAstBuilder->addStmt($methodReturnNode); | |
$this->methodAsts[] = $methodAstBuilder; | |
return $methodAstBuilder; | |
} | |
public function buildConstructorDeclaration(ClassMethod $method) | |
{ | |
$methodAstBuilder = $this->methodDeclarationPrelude($method); | |
//Assign new Instance of subject | |
$assignSubjectInstanceNode = new Assign( | |
$this->factory->propertyFetch( | |
$this->factory->var('this'), | |
$this->factory->val('subjectInstance') | |
), | |
$this->factory->new($this->subjectClassShortName, [$this->factory->var('args')]) | |
); | |
//we want to unpack args when calling original constructor | |
$assignSubjectInstanceNode->expr->args[0]->unpack = true; | |
$assignInstanceExpression = new Expression( | |
$assignSubjectInstanceNode | |
); | |
$methodAstBuilder = $methodAstBuilder->addStmt( | |
$assignInstanceExpression); | |
$this->methodAsts[] = $methodAstBuilder; | |
return $methodAstBuilder; | |
} | |
protected function methodDeclarationPrelude(ClassMethod $method) | |
{ | |
$name = $method->name; | |
$methodAstBuilder = $this->factory->method($name); | |
$methodAstBuilder = $this->addMethodParams($methodAstBuilder, $method->getParams()); | |
//Get the args method was called with | |
$assignNode = new Assign( | |
$this->factory->var('args'), | |
$this->factory->funcCall('func_get_args', []) | |
); | |
$assignExpression = new Expression( | |
$assignNode | |
); | |
$methodAstBuilder = $methodAstBuilder->addStmt( | |
$assignExpression); | |
return $methodAstBuilder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment