diff --git a/.travis.yml b/.travis.yml index bb6c4423d..65abd25d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ php: - hhvm - nightly +services: + - mysql + matrix: include: - php: "5.3" @@ -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" diff --git a/composer.json b/composer.json index 440a405b9..de07bf918 100644 --- a/composer.json +++ b/composer.json @@ -15,5 +15,8 @@ }, "autoload": { "psr-0": { "Doctrine_": "lib/" } + }, + "autoload-dev": { + "psr-4": { "": "tests/autoloaded/" } } } diff --git a/lib/Doctrine/Event.php b/lib/Doctrine/Event.php index 408d8ba13..74975c787 100644 --- a/lib/Doctrine/Event.php +++ b/lib/Doctrine/Event.php @@ -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 diff --git a/lib/Doctrine/Record.php b/lib/Doctrine/Record.php index 386286d29..60e95d8e8 100644 --- a/lib/Doctrine/Record.php +++ b/lib/Doctrine/Record.php @@ -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 diff --git a/lib/Doctrine/Record/Generator.php b/lib/Doctrine/Record/Generator.php index 728207ee0..90e8b1744 100644 --- a/lib/Doctrine/Record/Generator.php +++ b/lib/Doctrine/Record/Generator.php @@ -153,6 +153,7 @@ 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(); @@ -160,10 +161,28 @@ public function initialize(Doctrine_Table $table) $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(); @@ -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()); + } } /** @@ -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); diff --git a/lib/Doctrine/Search.php b/lib/Doctrine/Search.php index f6449b3c4..e6a189bf2 100644 --- a/lib/Doctrine/Search.php +++ b/lib/Doctrine/Search.php @@ -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(); diff --git a/lib/Doctrine/Table.php b/lib/Doctrine/Table.php index 4cf111a0c..b09c15966 100644 --- a/lib/Doctrine/Table.php +++ b/lib/Doctrine/Table.php @@ -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'])); @@ -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); diff --git a/tests/Connection/CustomTestCase.php b/tests/Connection/CustomTestCase.php index badb87d77..19c3e2f9a 100644 --- a/tests/Connection/CustomTestCase.php +++ b/tests/Connection/CustomTestCase.php @@ -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 diff --git a/tests/DoctrineTest/UnitTestCase.php b/tests/DoctrineTest/UnitTestCase.php index 681079d03..fcfc029f3 100644 --- a/tests/DoctrineTest/UnitTestCase.php +++ b/tests/DoctrineTest/UnitTestCase.php @@ -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(); diff --git a/tests/ManagerTestCase.php b/tests/ManagerTestCase.php index 4765a6256..f9becd851 100644 --- a/tests/ManagerTestCase.php +++ b/tests/ManagerTestCase.php @@ -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() { } diff --git a/tests/Record/GeneratorTestCase.php b/tests/Record/GeneratorTestCase.php index c9e963278..eb1597563 100644 --- a/tests/Record/GeneratorTestCase.php +++ b/tests/Record/GeneratorTestCase.php @@ -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 @@ -69,4 +85,8 @@ public function setUp() { $this->actAs('I18n', array('fields' => array('title'))); } -} \ No newline at end of file +} + +class I18nGeneratorModelAutoload extends I18nGeneratorComponentBinding +{ +} diff --git a/tests/TableTestCase.php b/tests/TableTestCase.php index 793e9672e..081962881 100644 --- a/tests/TableTestCase.php +++ b/tests/TableTestCase.php @@ -36,6 +36,7 @@ class Doctrine_Table_TestCase extends Doctrine_UnitTestCase public function prepareTables() { $this->tables[] = 'FieldNameTest'; + $this->tables[] = 'I18nFilterTest'; parent::prepareTables(); } @@ -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); diff --git a/tests/autoloaded/I18nGeneratorModelAutoloadTranslation.php b/tests/autoloaded/I18nGeneratorModelAutoloadTranslation.php new file mode 100644 index 000000000..7bb580a4d --- /dev/null +++ b/tests/autoloaded/I18nGeneratorModelAutoloadTranslation.php @@ -0,0 +1,9 @@ +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]; + } + } +}