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

[WIP] Fix more than one record generator initialisation on same php process #55

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
11 changes: 10 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ php:
- hhvm
- nightly

services:
- mysql

matrix:
include:
- php: "5.3"
Expand All @@ -20,5 +23,11 @@ matrix:
- php: nightly
- php: hhvm

before_install:
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'

before_script:
- composer install

script:
- php -dshort_open_tag=Off -dmagic_quotes_gpc=Off tests/index.php
- "cd tests && php -dshort_open_tag=Off -dmagic_quotes_gpc=Off run.php"
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
},
"autoload": {
"psr-0": { "Doctrine_": "lib/" }
},
"autoload-dev": {
"psr-4": { "": "tests/autoloaded/" }
}
}
1 change: 1 addition & 0 deletions lib/Doctrine/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Doctrine_Event
const RECORD_DQL_SELECT = 28;
const RECORD_DQL_UPDATE = 29;
const RECORD_VALIDATE = 30;
const RECORD_POST_SETUP = 31;

/**
* @var mixed $_nextSequence the sequence of the next event that will be created
Expand Down
7 changes: 7 additions & 0 deletions lib/Doctrine/Record.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,13 @@ public function preHydrate($event)
public function postHydrate($event)
{ }

/**
* Empty template method to provide Record classes with the ability to alter setup
* after it runs
*/
public function postSetUp($event)
{ }

/**
* Get the record error stack as a human readable string.
* Useful for outputting errors to user via web browser
Expand Down
36 changes: 31 additions & 5 deletions lib/Doctrine/Record/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,36 @@ public function initialize(Doctrine_Table $table)
$ownerClassName = $this->_options['table']->getComponentName();
$className = $this->_options['className'];
$this->_options['className'] = str_replace('%CLASS%', $ownerClassName, $className);
$componentName = $this->_options['className'];

if (isset($this->_options['tableName'])) {
$ownerTableName = $this->_options['table']->getTableName();
$tableName = $this->_options['tableName'];
$this->_options['tableName'] = str_replace('%TABLE%', $ownerTableName, $tableName);
}

// check that class doesn't exist (otherwise we cannot create it)
if ($this->_options['generateFiles'] === false && class_exists($this->_options['className'])) {
$this->_table = Doctrine_Core::getTable($this->_options['className']);
return false;
$connection = $table->getConnection();
$hasTableCache = $connection->getAttribute(Doctrine_Core::ATTR_TABLE_CACHE);
if ($hasTableCache) {
// Load from cache
$tableCacheDriver = $connection->getTableCacheDriver();
$hash = md5($componentName.'DOCTRINE_TABLE_CACHE_SALT');
$cached = $tableCacheDriver->fetch($hash);

if ($cached) {
$this->_table = unserialize($cached);
$this->_table->initializeFromCache($connection, $this);

$connection->addTable($this->_table);

$this->buildRelation();

$this->generateClassFromTable($this->_table);

$this->buildChildDefinitions();

return;
}
}

$this->buildTable();
Expand All @@ -182,6 +201,11 @@ public function initialize(Doctrine_Table $table)
$this->buildChildDefinitions();

$this->_table->initIdentifier();

if ($hasTableCache) {
// Save cached table
$tableCacheDriver->save($hash, serialize($this->_table), $connection->getTableCacheLifeSpan());
}
}

/**
Expand Down Expand Up @@ -461,7 +485,9 @@ public function generateClass(array $definition = array())
} else {
throw new Doctrine_Record_Exception('If you wish to generate files then you must specify the path to generate the files in.');
}
} else {
} elseif (!class_exists($definition['className'])) {
// The class is not defined then we can load the definition.

$def = $builder->buildDefinition($definition);

eval($def);
Expand Down
5 changes: 0 additions & 5 deletions lib/Doctrine/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,6 @@ public function setTableDefinition()

$className = $this->getOption('className');

$autoLoad = (bool) ($this->_options['generateFiles']);
if (class_exists($className, $autoLoad)) {
return false;
}

// move any columns currently in the primary key to the end
// So that 'keyword' is the first field in the table
$previousIdentifier = array();
Expand Down
8 changes: 8 additions & 0 deletions lib/Doctrine/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ public function __construct($name, Doctrine_Connection $conn, $initDefinition =
if ($this->isTree()) {
$this->getTree()->setUp();
}

$event = new Doctrine_Event($this->record, Doctrine_Event::RECORD_POST_SETUP);

$this->record->postSetUp($event);
} else {
if ( ! isset($this->_options['tableName'])) {
$this->setTableName(Doctrine_Inflector::tableize($this->_options['name']));
Expand Down Expand Up @@ -3057,6 +3061,10 @@ public function initializeFromCache(Doctrine_Connection $conn)
$this->getTree()->setUp();
}

$event = new Doctrine_Event($this->record, Doctrine_Event::RECORD_POST_SETUP);

$this->record->postSetUp($event);

$this->_filters[] = new Doctrine_Record_Filter_Standard();
if ($this->getAttribute(Doctrine_Core::ATTR_USE_TABLE_REPOSITORY)) {
$this->_repository = new Doctrine_Table_Repository($this);
Expand Down
5 changes: 4 additions & 1 deletion tests/Connection/CustomTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ public function testConnection()

class Doctrine_Connection_Test extends Doctrine_Connection_Common
{

/**
* @var string $driverName the name of this connection driver
*/
protected $driverName = 'Mock';
}

class Doctrine_Adapter_Test implements Doctrine_Adapter_Interface
Expand Down
8 changes: 8 additions & 0 deletions tests/DoctrineTest/UnitTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ public function fail($message = "")
$this->_fail($message);
}

public function failFromException($e)
{
$this->fail(sprintf('Unexpected exception "%s" %s',
$e->getMessage(),
"\n\n".$e->getTraceAsString()
));
}

public function _fail($message = "")
{
$trace = debug_backtrace();
Expand Down
20 changes: 15 additions & 5 deletions tests/ManagerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,24 @@ public function testDropDatabases()

public function testConnectionInformationDecoded()
{
$e = null;
$dsn = 'mysql://' . urlencode('test/t') . ':' . urlencode('p@ssword') . '@localhost/' . urlencode('db/name');

$conn = Doctrine_Manager::connection($dsn);
$options = $conn->getOptions();
try {
$conn = Doctrine_Manager::connection($dsn);
$options = $conn->getOptions();

$this->assertEqual($options['username'], 'test/t');
$this->assertEqual($options['password'], 'p@ssword');
$this->assertEqual($options['dsn'], 'mysql:host=localhost;dbname=db/name');
$this->assertEqual($options['username'], 'test/t');
$this->assertEqual($options['password'], 'p@ssword');
$this->assertEqual($options['dsn'], 'mysql:host=localhost;dbname=db/name');
} catch (Exception $e) {
}

Doctrine_Manager::getInstance()->closeConnection($conn);

if (null !== $e) {
throw $e;
}
}
public function prepareData() { }
public function prepareTables() { }
Expand Down
22 changes: 21 additions & 1 deletion tests/Record/GeneratorTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ public function testGeneratorComponentBinding()
$this->fail($e->getMessage());
}
}

public function testGeneratorModelAutoload()
{
Doctrine_Manager::connection('sqlite::memory:', 'test_tmp_conn', false);
Doctrine_Manager::getInstance()->bindComponent('I18nGeneratorModelAutoload', 'test_tmp_conn');
Doctrine_Core::createTablesFromArray(array('I18nGeneratorModelAutoload'));

try {
$record = new I18nGeneratorModelAutoload();
$record->Translation['EN']->title = 'en test';

$this->fail('The generated record class is autoloadable then it must be used.');
} catch (LogicException $e) {
$this->assertEqual('This record is expected to be instanciated as generateFiles option on record generator is false and it is autoloadable.', $e->getMessage());
}
}
}

class I18nGeneratorComponentBinding extends Doctrine_Record
Expand All @@ -69,4 +85,8 @@ public function setUp()
{
$this->actAs('I18n', array('fields' => array('title')));
}
}
}

class I18nGeneratorModelAutoload extends I18nGeneratorComponentBinding
{
}
53 changes: 53 additions & 0 deletions tests/TableTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Doctrine_Table_TestCase extends Doctrine_UnitTestCase
public function prepareTables()
{
$this->tables[] = 'FieldNameTest';
$this->tables[] = 'I18nFilterTest';
parent::prepareTables();
}

Expand All @@ -57,6 +58,58 @@ public function testSerialize()
$this->assertEqual($table->getColumns(), $unserializedTable->getColumns());
}

public function testTableCacheWithI18nFilter()
{
try {
// Remove the table from internal class cache.
$tables = $this->conn->getTables();
$relf = new ReflectionProperty($this->conn, 'tables');
unset($tables['I18nFilterTest']);
unset($tables['I18nFilterTestTranslation']);
$relf->setAccessible(true);
$relf->setValue($this->conn, $tables);
$relf->setAccessible(false);

$this->conn->setAttribute(Doctrine_Core::ATTR_TABLE_CACHE, $cache = new Doctrine_Cache_Array());
$table = $this->conn->getTable('I18nFilterTest');

// The cache is filled.
$this->assertTrue($cache->contains(md5('I18nFilterTestDOCTRINE_TABLE_CACHE_SALT')));
$this->assertTrue($cache->contains(md5('I18nFilterTestTranslationDOCTRINE_TABLE_CACHE_SALT')));

$record = $table->create();
$record['name'] = 'foo';
$this->assertEqual('foo', $record['name']);

// Test the I18nFilterTest record that include the second filter.
$this->assertTrue(in_array('I18nFilterTestFilter', array_map('get_class', $table->getFilters())));
$expectedFilterNames = array_map('get_class', $table->getFilters());

// Remove the table from internal class cache.
$tables = $this->conn->getTables();
$relf = new ReflectionProperty($this->conn, 'tables');
unset($tables['I18nFilterTest']);
unset($tables['I18nFilterTestTranslation']);
$relf->setAccessible(true);
$relf->setValue($this->conn, $tables);
$relf->setAccessible(false);

// Get table from cache.
$cachedTable = $this->conn->getTable('I18nFilterTest');

$record = $cachedTable->create();

$this->assertEqual($expectedFilterNames, array_map('get_class', $cachedTable->getFilters()));

$record['name'] = 'foo';
$this->assertEqual('foo', $record['name']);
} catch (Exception $e) {
$this->failFromException($e);
}

$this->conn->setAttribute(Doctrine_Core::ATTR_TABLE_CACHE, null);
}

public function testFieldConversion()
{
$this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
Expand Down
9 changes: 9 additions & 0 deletions tests/autoloaded/I18nGeneratorModelAutoloadTranslation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class I18nGeneratorModelAutoloadTranslation extends Doctrine_Record
{
public function __construct()
{
throw new LogicException('This record is expected to be instanciated as generateFiles option on record generator is false and it is autoloadable.');
}
}
2 changes: 1 addition & 1 deletion tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

define('DOCTRINE_DIR', $_SERVER['DOCTRINE_DIR']);

require_once(DOCTRINE_DIR . '/lib/Doctrine/Core.php');
require_once DOCTRINE_DIR.'/vendor/autoload.php';

spl_autoload_register(array('Doctrine_Core', 'autoload'));
spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));
Expand Down
58 changes: 58 additions & 0 deletions tests/models/I18nFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

class I18nFilterTest extends Doctrine_Record
{
/**
* {@inheritdoc}
*/
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 200);
$this->hasColumn('title', 'string', 200);
}

/**
* {@inheritdoc}
*/
public function setUp()
{
$this->actAs('I18n', array('fields' => array('name', 'title')));
}

/**
* {@inheritdoc}
*/
public function postSetUp($event)
{
parent::postSetUp($event);

$table = $this->getTable();

if ($table->hasRelation('Translation')) {
$table->unshiftFilter(new I18nFilterTestFilter());
}
}
}

class I18nFilterTestFilter extends Doctrine_Record_Filter_Standard
{
/**
* {@inheritdoc}
*/
public function filterSet(Doctrine_Record $record, $name, $value)
{
return $record['Translation']['en'][$name] = $value;
}

/**
* {@inheritdoc}
*/
public function filterGet(Doctrine_Record $record, $name)
{
$trans = $record['Translation'];

if (isset($trans['en'])) {
return $trans['en'][$name];
}
}
}