Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reusable approach to enhanced proxy generation #8

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions DependencyInjection/Compiler/PointcutMatchingPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
class PointcutMatchingPass implements CompilerPassInterface
{
private $pointcuts;
private $cacheDir;
private $container;

public function __construct(array $pointcuts = null)
Expand All @@ -51,7 +50,6 @@ public function __construct(array $pointcuts = null)
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->cacheDir = $container->getParameter('jms_aop.cache_dir').'/proxies';
$pointcuts = $this->getPointcuts();

$interceptors = array();
Expand Down Expand Up @@ -150,12 +148,11 @@ private function processDefinition(Definition $definition, $pointcuts, &$interce
if ($file) {
$generator->setRequiredFile($file);
}
$enhancer = new Enhancer($class, array(), array(
$generator
));
$enhancer->writeClass($filename = $this->cacheDir.'/'.str_replace('\\', '-', $class->name).'.php');
$definition->setFile($filename);
$definition->setClass($enhancer->getClassName($class));

$matcher = $this->container->get('jms_aop.proxy_matcher');
$proxy = $matcher->getEnhanced($definition);
$proxy->addGenerator($generator);

$definition->addMethodCall('__CGInterception__setLoader', array(
new Reference('jms_aop.interceptor_loader')
));
Expand Down
18 changes: 18 additions & 0 deletions DependencyInjection/Compiler/WriteProxiesPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class WriteProxiesPass implements CompilerPassInterface
{
function process(ContainerBuilder $container)
{
$matcher = $container->get('jms_aop.proxy_matcher');
$matcher->writeProxyFiles();
}
}
2 changes: 2 additions & 0 deletions JMSAopBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use JMS\AopBundle\DependencyInjection\Compiler\PointcutMatchingPass;
use JMS\AopBundle\DependencyInjection\Compiler\WriteProxiesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -30,5 +31,6 @@ class JMSAopBundle extends Bundle
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new PointcutMatchingPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new WriteProxiesPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
57 changes: 57 additions & 0 deletions Proxy/ProxyMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Proxy;

use Symfony\Component\DependencyInjection\Definition;
use CG\Proxy\GeneratorInterface;

class ProxyMatcher
{
protected $cacheDir;

/**
* @var \SplObjectStorage
*/
protected $storage;

public function __construct($cacheDir)
{
$this->cacheDir = $cacheDir;
$this->storage = new \SplObjectStorage();
}

/**
* @param Definition $definition
* @return ProxyPromise
*/
public function getEnhanced(Definition $definition)
{
if (isset($this->storage[$definition])) {
return $this->storage[$definition];
}

$promise = new ProxyPromise($this);
$this->storage[$definition] = $promise;
return $promise;
}

public function writeProxyFiles()
{
foreach($this->storage as $definition) {
$promise = $this->storage[$definition];

$class = new \ReflectionClass($definition->getClass());
$enhancer = $promise->getEnhancer($class);

$filename = $this->cacheDir.'/'.str_replace('\\', '-', $class->name).'.php';
$proxyClassName = $enhancer->getClassName($class);

$enhancer->writeClass($filename);
$definition->setFile($filename);
$definition->setClass($proxyClassName);
}
}
}
27 changes: 27 additions & 0 deletions Proxy/ProxyPromise.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Proxy;
use CG\Proxy\GeneratorInterface;
use CG\Proxy\Enhancer;
use ReflectionClass;

class ProxyPromise
{
/**
* @var GeneratorInterface[]
*/
private $generators = array();

public function addGenerator(GeneratorInterface $generator)
{
$this->generators[] = $generator;
}

public function getEnhancer(ReflectionClass $class)
{
return new Enhancer($class, array(), $this->generators);
}
}
5 changes: 5 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<parameters>
<parameter key="jms_aop.interceptor_loader.class">JMS\AopBundle\Aop\InterceptorLoader</parameter>
<parameter key="jms_aop.proxy_matcher.class">JMS\AopBundle\Proxy\ProxyMatcher</parameter>
</parameters>

<services>
Expand All @@ -14,5 +15,9 @@
<service id="jms_aop.interceptor_loader" class="%jms_aop.interceptor_loader.class%">
<argument type="service" id="service_container" />
</service>

<service id="jms_aop.proxy_matcher" class="%jms_aop.proxy_matcher.class%">
<argument>%jms_aop.cache_dir%/proxies</argument>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
use JMS\AopBundle\DependencyInjection\JMSAopExtension;
use JMS\AopBundle\DependencyInjection\Compiler\PointcutMatchingPass;
use Symfony\Component\HttpKernel\Util\Filesystem;
use JMS\AopBundle\DependencyInjection\Compiler\WriteProxiesPass;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class PointcutMatchingPassTest extends \PHPUnit_Framework_TestCase
Expand Down Expand Up @@ -92,5 +93,8 @@ private function process(ContainerBuilder $container)

$pass = new PointcutMatchingPass();
$pass->process($container);

$pass = new WriteProxiesPass();
$pass->process($container);
}
}
26 changes: 26 additions & 0 deletions Tests/Proxy/Fixture/AnotherPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Tests\Proxy\Fixture;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use JMS\AopBundle\Tests\Proxy\Fixture\SomeGenerator;

class AnotherPass implements CompilerPassInterface
{
function process(ContainerBuilder $container)
{
$methodName = '__ultimatelyAnotherMethod';
$matcher = $container->get('jms_aop.proxy_matcher');
$generator = new SomeGenerator($methodName);

$definition = $container->getDefinition('test');

$proxy = $matcher->getEnhanced($definition);
$proxy->addGenerator($generator);
$definition->addMethodCall($methodName);
}
}
26 changes: 26 additions & 0 deletions Tests/Proxy/Fixture/FirstPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Tests\Proxy\Fixture;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use JMS\AopBundle\Tests\Proxy\Fixture\SomeGenerator;

class FirstPass implements CompilerPassInterface
{
function process(ContainerBuilder $container)
{
$methodName = '__fistPassMethod';
$matcher = $container->get('jms_aop.proxy_matcher');
$generator = new SomeGenerator($methodName);

$definition = $container->getDefinition('test');

$proxy = $matcher->getEnhanced($definition);
$proxy->addGenerator($generator);
$definition->addMethodCall($methodName);
}
}
34 changes: 34 additions & 0 deletions Tests/Proxy/Fixture/SomeGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Tests\Proxy\Fixture;

use CG\Proxy\GeneratorInterface;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need to decouple the proxies from CG lib or if this is desired, even in Doctrine, i think they are generating by hand the proxies?

use CG\Generator\PhpClass;
use CG\Generator\PhpMethod;

class SomeGenerator implements GeneratorInterface
{
public $methodName;

public function __construct($methodName)
{
$this->methodName = $methodName;
}

/**
* Generates the necessary changes in the class.
*
* @param \ReflectionClass $originalClass
* @param PhpClass $generatedClass The generated class
* @return void
*/
function generate(\ReflectionClass $originalClass, PhpClass $generatedClass)
{
$method = PhpMethod::create($this->methodName)
->setBody("\$this->things[] = '{$this->methodName}';");
$generatedClass->setMethod($method);
}
}
16 changes: 16 additions & 0 deletions Tests/Proxy/Fixture/TestService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Tests\Proxy\Fixture;

class TestService
{
protected $things = array();

public function getThings()
{
return $this->things;
}
}
78 changes: 78 additions & 0 deletions Tests/Proxy/ProxyMatcherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* @author nfx
*/

namespace JMS\AopBundle\Tests\Proxy;

use JMS\AopBundle\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
use JMS\AopBundle\DependencyInjection\JMSAopExtension;
use JMS\AopBundle\DependencyInjection\Compiler\WriteProxiesPass;
use JMS\AopBundle\Tests\Proxy\Fixture\FirstPass;
use JMS\AopBundle\Tests\Proxy\Fixture\AnotherPass;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ProxyMatcherTest extends \PHPUnit_Framework_TestCase
{
private $cacheDir;
private $fs;

public function testProcess()
{
$container = $this->getContainer();
$container->register('test', 'JMS\AopBundle\Tests\Proxy\Fixture\TestService');

$this->process($container);

$service = $container->get('test');
$this->assertInstanceOf('JMS\AopBundle\Tests\Proxy\Fixture\TestService', $service);

$this->assertEquals(array('__fistPassMethod', '__ultimatelyAnotherMethod'), $service->getThings());
}

protected function setUp()
{
$this->cacheDir = sys_get_temp_dir() . '/jms_aop_test';
$this->fs = new Filesystem();

if (is_dir($this->cacheDir)) {
$this->fs->remove($this->cacheDir);
}

if (false === @mkdir($this->cacheDir, 0777, true)) {
throw new RuntimeException(sprintf('Could not create cache dir "%s".', $this->cacheDir));
}
}

protected function tearDown()
{
$this->fs->remove($this->cacheDir);
}

private function getContainer()
{
$container = new ContainerBuilder();

$extension = new JMSAopExtension();
$extension->load(array(array('cache_dir' => $this->cacheDir)), $container);

return $container;
}

private function process(ContainerBuilder $container)
{
$pass = new ResolveParameterPlaceHoldersPass();
$pass->process($container);

$pass = new FirstPass();
$pass->process($container);

$pass = new AnotherPass();
$pass->process($container);

$pass = new WriteProxiesPass();
$pass->process($container);
}
}
15 changes: 15 additions & 0 deletions Tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

// this file searches for the autoload file of your project, and includes it
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder why not composer autoload would be enough?

$dir = __DIR__;
$lastDir = null;
while (($dir = dirname($dir)) && $dir !== $lastDir) {
$lastDir = $dir;

if (file_exists($file = $dir.'/app/autoload.php')) {
require_once $file;
return;
}
}

throw new RuntimeException('Could not locate the project\'s bootstrap.php.cache. If your bundle is not inside a project, you need to replace this bootstrap file.');
Loading