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

[DDC-717] Add eval() and FILE_NOT_EXISTS strategies for proxy generation #291

Merged
merged 5 commits into from Aug 20, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions .travis.yml
@@ -1,8 +1,5 @@
language: php

env:
- OPCODE_CACHE=apc

php:
- 5.3.3
- 5.3
Expand All @@ -11,4 +8,3 @@ php:

before_script:
- composer --prefer-source install
- php ./bin/travis-setup.php $OPCODE_CACHE
141 changes: 0 additions & 141 deletions bin/travis-setup.php

This file was deleted.

65 changes: 59 additions & 6 deletions lib/Doctrine/Common/Proxy/AbstractProxyFactory.php
Expand Up @@ -31,6 +31,43 @@
*/
abstract class AbstractProxyFactory
{
/**
* Never autogenerate a proxy and rely that it was generated by some
* process before deployment.
*
* @var integer
*/
const AUTOGENERATE_NEVER = 0;

/**
* Always generates a new proxy in every request.
*
* This is only sane during development.
*
* @var integer
*/
const AUTOGENERATE_ALWAYS = 1;

/**
* Autogenerate the proxy class when the proxy file does not exist.
*
* This strategy causes a file exists call whenever any proxy is used the
* first time in a request.
*
* @var integer
*/
const AUTOGENERATE_FILE_NOT_EXISTS = 2;

/**
* Generate the proxy classes using eval().
*
* This strategy is only sane for development, and even then it gives me
* the creeps a little.
*
* @var integer
*/
const AUTOGENERATE_EVAL = 3;

/**
* @var \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory
*/
Expand All @@ -54,13 +91,13 @@ abstract class AbstractProxyFactory
/**
* @param \Doctrine\Common\Proxy\ProxyGenerator $proxyGenerator
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory $metadataFactory
* @param bool $autoGenerate
* @param bool|int $autoGenerate
*/
public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate)
{
$this->proxyGenerator = $proxyGenerator;
$this->metadataFactory = $metadataFactory;
$this->autoGenerate = $autoGenerate;
$this->autoGenerate = (int)$autoGenerate;
Copy link
Member

Choose a reason for hiding this comment

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

Space

}

/**
Expand Down Expand Up @@ -158,11 +195,27 @@ private function getProxyDefinition($className)
if ( ! class_exists($proxyClassName, false)) {
$fileName = $this->proxyGenerator->getProxyFileName($className);

if ($this->autoGenerate) {
$this->proxyGenerator->generateProxyClass($classMetadata);
switch ($this->autoGenerate) {
case self::AUTOGENERATE_NEVER:
require $fileName;
break;

case self::AUTOGENERATE_FILE_NOT_EXISTS:
if ( ! file_exists($fileName)) {
$this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
}
Copy link
Member

Choose a reason for hiding this comment

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

Newline after this line

require $fileName;
break;

case self::AUTOGENERATE_ALWAYS:
$this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
require $fileName;
break;

case self::AUTOGENERATE_EVAL:
$this->proxyGenerator->generateProxyClass($classMetadata, false);
break;
}

require $fileName;
}

return $this->definitions[$className];
Expand Down
18 changes: 14 additions & 4 deletions lib/Doctrine/Common/Proxy/ProxyGenerator.php
Expand Up @@ -254,11 +254,11 @@ public function setProxyClassTemplate($proxyClassTemplate)
* Generates a proxy class file.
*
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class.
* @param string $fileName Filename (full path) for the generated class.
* @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used.
*
* @throws UnexpectedValueException
*/
public function generateProxyClass(ClassMetadata $class, $fileName = null)
public function generateProxyClass(ClassMetadata $class, $fileName = false)
Copy link
Member

Choose a reason for hiding this comment

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

Changing this to false is unnecessary

{
preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);

Expand All @@ -277,8 +277,18 @@ public function generateProxyClass(ClassMetadata $class, $fileName = null)
}
}

$proxyCode = strtr($this->proxyClassTemplate, $placeholders);
$fileName = $fileName ?: $this->getProxyFileName($class->getName());
$proxyCode = strtr($this->proxyClassTemplate, $placeholders);

if ( ! $fileName) {
$proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class);

if ( ! class_exists($proxyClassName)) {
eval(substr($proxyCode, 5));
}

return;
}

$parentDirectory = dirname($fileName);

if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
Expand Down
70 changes: 47 additions & 23 deletions tests/Doctrine/Tests/Common/Proxy/ProxyClassGeneratorTest.php
Expand Up @@ -61,8 +61,7 @@ protected function setUp()
return;
}

$this->proxyGenerator->generateProxyClass($this->metadata);
require_once $this->proxyGenerator->getProxyFileName($this->metadata->getName());
$this->generateAndRequire($this->proxyGenerator, $this->metadata);
}

public function testReferenceProxyRespectsMethodsParametersTypeHinting()
Expand Down Expand Up @@ -107,38 +106,37 @@ public function testNonNamespacedProxyGeneration()
public function testClassWithSleepProxyGeneration()
{
if (!class_exists('Doctrine\Tests\Common\ProxyProxy\__CG__\SleepClass', false)) {
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$reflClass = new ReflectionClass('Doctrine\Tests\Common\Proxy\SleepClass');
$metadata->expects($this->any())->method('getReflectionClass')->will($this->returnValue($reflClass));
$metadata->expects($this->any())->method('getIdentifierFieldNames')->will($this->returnValue(array('id')));
$metadata->expects($this->any())->method('getName')->will($this->returnValue($reflClass->getName()));
$className = 'Doctrine\Tests\Common\Proxy\SleepClass';
$metadata = $this->createClassMetadata($className, array('id'));
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);
$proxyGenerator->generateProxyClass($metadata);
require_once $proxyGenerator->getProxyFileName($metadata->getName());

$this->generateAndRequire($proxyGenerator, $metadata);
}

$classCode = file_get_contents(__DIR__ . '/generated/__CG__DoctrineTestsCommonProxySleepClass.php');
$this->assertEquals(1, substr_count($classCode, 'function __sleep'));
$this->assertEquals(1, substr_count($classCode, 'parent::__sleep()'));
}

private function generateAndRequire($proxyGenerator, $metadata)
{
$proxyGenerator->generateProxyClass($metadata, $proxyGenerator->getProxyFileName($metadata->getName()));

require_once $proxyGenerator->getProxyFileName($metadata->getName());
Copy link
Member

Choose a reason for hiding this comment

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

Missing line break

}

public function testClassWithCallableTypeHintOnProxiedMethod()
{
if (PHP_VERSION_ID < 50400) {
$this->markTestSkipped('`callable` is only supported in PHP >=5.4.0');
}

if (!class_exists('Doctrine\Tests\Common\ProxyProxy\__CG__\CallableTypeHintClass', false)) {
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$reflClass = new ReflectionClass('Doctrine\Tests\Common\Proxy\CallableTypeHintClass');
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);

$metadata->expects($this->any())->method('getReflectionClass')->will($this->returnValue($reflClass));
$metadata->expects($this->any())->method('getIdentifierFieldNames')->will($this->returnValue(array('id')));
$metadata->expects($this->any())->method('getName')->will($this->returnValue($reflClass->getName()));
$className = 'Doctrine\Tests\Common\Proxy\CallableTypeHintClass';
$metadata = $this->createClassMetadata($className, array('id'));

$proxyGenerator->generateProxyClass($metadata);
require_once $proxyGenerator->getProxyFileName($metadata->getName());
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);
$this->generateAndRequire($proxyGenerator, $metadata);
}

$classCode = file_get_contents(__DIR__ . '/generated/__CG__DoctrineTestsCommonProxyCallableTypeHintClass.php');
Expand All @@ -149,11 +147,7 @@ public function testClassWithCallableTypeHintOnProxiedMethod()
public function testClassWithInvalidTypeHintOnProxiedMethod()
{
$className = 'Doctrine\Tests\Common\Proxy\InvalidTypeHintClass';
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$reflClass = new ReflectionClass($className);
$metadata->expects($this->any())->method('getReflectionClass')->will($this->returnValue($reflClass));
$metadata->expects($this->any())->method('getIdentifierFieldNames')->will($this->returnValue(array()));
$metadata->expects($this->any())->method('getName')->will($this->returnValue($className));
$metadata = $this->createClassMetadata($className, array('id'));
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);

$this->setExpectedException(
Expand Down Expand Up @@ -182,4 +176,34 @@ public function testInvalidPlaceholderThrowsException()
$generator = new ProxyGenerator(__DIR__ . '/generated', 'SomeNamespace');
$generator->setPlaceholder('<somePlaceholder>', array());
}

public function testUseEvalIfNoFilenameIsGiven()
{
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);

$className = __NAMESPACE__ . '\\EvalBase';

$metadata = $this->createClassMetadata($className, array('id'));

$proxyGenerator->generateProxyClass($metadata);

$reflClass = new ReflectionClass('Doctrine\Tests\Common\ProxyProxy\__CG__\Doctrine\Tests\Common\Proxy\EvalBase');

$this->assertContains("eval()'d code", $reflClass->getFileName());
}

private function createClassMetadata($className, array $ids)
{
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$reflClass = new ReflectionClass($className);
$metadata->expects($this->any())->method('getReflectionClass')->will($this->returnValue($reflClass));
$metadata->expects($this->any())->method('getIdentifierFieldNames')->will($this->returnValue($ids));
$metadata->expects($this->any())->method('getName')->will($this->returnValue($className));

return $metadata;
}
}

class EvalBase
Copy link
Member

Choose a reason for hiding this comment

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

Can you move this to another file?

{
}