Reputation: 76
After some ORM framework research, I decided to use propel for the first time. Everything worked perfectly until I started to work on a many-to-many relationship for the caching mechanism. After struggling with getting a cache entry inserted with multiple cache tags (using transactions, without persisting all objects manually, ...) I face the problem of querying a cache entry by a given cache tag. Using filterByTag (which causes the use of a useTagQuery and so on) always ends in an Exception
Cannot fetch ColumnMap for undefined column: cache_id
The causing piece of code:
/**
* Drops all cache entries which are associated to the given tag
*
* @param $tag string The tag to drop
* @return void
*/
public function dropTag($tag) {
$cTag = CacheTagQuery::create()->findOneByTag($tag);
if($cTag instanceof CacheTag) {
$cEntries = CacheQuery::create()->filterByTag($cTag)->find();
foreach($cEntries as $cEntry) {
if($cEntry instanceof Cache) {
$cEntry->delete();
}
}
}
}
Relevant part of schema.xml:
<table name="tag">
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
<column name="tag" type="VARCHAR" size="255" />
<column name="type" type="INTEGER" inheritance="single">
<inheritance key="1" class="Tag"/>
<inheritance key="2" class="CacheTag" extends="Tag"/>
</column>
<behavior name="cachedrop" />
</table>
<table name="cache">
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
<column name="crdate" type="TIMESTAMP" />
<behavior name="timestampable">
<parameter name="create_column" value="crdate" />
<parameter name="disable_updated_at" value="true" />
</behavior>
<column name="lifetime" type="INTEGER" defaultValue="-1" />
<column name="name" type="VARCHAR" />
<column name="content" type="CLOB" size="4294967296" required="true" />
</table>
<table name="cache_tag_mm" isCrossRef="true">
<column name="cache_id" type="INTEGER" primaryKey="true" />
<column name="tag_id" type="INTEGER" primaryKey="true" />
<foreign-key foreignTable="cache">
<reference local="cache_id" foreign="id"/>
</foreign-key>
<foreign-key foreignTable="tag">
<reference local="tag_id" foreign="id"/>
</foreign-key>
</table>
For inserting I have to persist each tag manually before persisting the cache entry:
try {
// create our new cache entry
$cEntry = new Cache();
$cEntry->setContent($content);
$cEntry->setLifetime($lifetime);
$cEntry->setName($cachetag);
if(count($processedTags) > 0) {
foreach($processedTags as $pTag) {
$cTag = CacheTagQuery::create()->filterByTag($pTag)->findOneOrCreate();
if($cTag->isNew()) {
$cTag->save();
}
$cEntry->addTag($cTag);
}
}
// finally persist the entry
$cEntry->save();
} catch(Exception $e) {
Logger::debugLog($e->getMessage());
}
Dev Environment:
PHP 5.3.15 with Suhosin-Patch (cli) (built: Aug 24 2012 17:45:44) Copyright (c) 1997-2012 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies with Zend Debugger v5.2, Copyright (c) 1999-2009, by Zend Technologies
Apache/2.2.22 (Unix)
Even the Many-To-Many example (http://propelorm.org/documentation/04-relationships.html#manytomany_relationships) on propel homepage causes this problem.
TagTableMap.php
class TagTableMap extends TableMap
{
/**
* The (dot-path) name of this class
*/
const CLASS_NAME = 'orm.map.TagTableMap';
/**
* Initialize the table attributes, columns and validators
* Relations are not initialized by this method since they are lazy loaded
*
* @return void
* @throws PropelException
*/
public function initialize()
{
// attributes
$this->setName('tag');
$this->setPhpName('Tag');
$this->setClassname('Tag');
$this->setPackage('orm');
$this->setUseIdGenerator(true);
$this->setSingleTableInheritance(true);
// columns
$this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null);
$this->addColumn('TAG', 'Tag', 'VARCHAR', false, 255, null);
$this->addColumn('TYPE', 'Type', 'INTEGER', false, null, null);
// validators
} // initialize()
/**
* Build the RelationMap objects for this table relationships
*/
public function buildRelations()
{
$this->addRelation('CacheTagMm', 'CacheTagMm', RelationMap::ONE_TO_MANY, array('id' => 'tag_id', ), null, null, 'CacheTagMms');
$this->addRelation('Cache', 'Cache', RelationMap::MANY_TO_MANY, array(), null, null, 'Caches');
} // buildRelations()
/**
*
* Gets the list of behaviors registered for this table
*
* @return array Associative array (name => parameters) of behaviors
*/
public function getBehaviors()
{
return array(
'cachedrop' => array(),
);
} // getBehaviors()
} // TagTableMap
CacheTableMap.php
class CacheTableMap extends TableMap
{
/**
* The (dot-path) name of this class
*/
const CLASS_NAME = 'orm.map.CacheTableMap';
/**
* Initialize the table attributes, columns and validators
* Relations are not initialized by this method since they are lazy loaded
*
* @return void
* @throws PropelException
*/
public function initialize()
{
// attributes
$this->setName('cache');
$this->setPhpName('Cache');
$this->setClassname('Cache');
$this->setPackage('orm');
$this->setUseIdGenerator(true);
// columns
$this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null);
$this->addColumn('CRDATE', 'Crdate', 'TIMESTAMP', false, null, null);
$this->addColumn('LIFETIME', 'Lifetime', 'INTEGER', false, null, -1);
$this->addColumn('NAME', 'Name', 'VARCHAR', false, 255, null);
$this->addColumn('CONTENT', 'Content', 'CLOB', true, 4294967296, null);
// validators
} // initialize()
/**
* Build the RelationMap objects for this table relationships
*/
public function buildRelations()
{
$this->addRelation('CacheTagMm', 'CacheTagMm', RelationMap::ONE_TO_MANY, array('id' => 'cache_id', ), null, null, 'CacheTagMms');
$this->addRelation('Tag', 'Tag', RelationMap::MANY_TO_MANY, array(), null, null, 'Tags');
} // buildRelations()
/**
*
* Gets the list of behaviors registered for this table
*
* @return array Associative array (name => parameters) of behaviors
*/
public function getBehaviors()
{
return array(
'timestampable' => array('create_column' => 'crdate', 'update_column' => 'updated_at', 'disable_updated_at' => 'true', ),
);
} // getBehaviors()
} // CacheTableMap
CacheTagMmTableMap.php
class CacheTagMmTableMap extends TableMap
{
/**
* The (dot-path) name of this class
*/
const CLASS_NAME = 'orm.map.CacheTagMmTableMap';
/**
* Initialize the table attributes, columns and validators
* Relations are not initialized by this method since they are lazy loaded
*
* @return void
* @throws PropelException
*/
public function initialize()
{
// attributes
$this->setName('cache_tag_mm');
$this->setPhpName('CacheTagMm');
$this->setClassname('CacheTagMm');
$this->setPackage('orm');
$this->setUseIdGenerator(true);
$this->setIsCrossRef(true);
// columns
$this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null);
$this->addForeignKey('CACHE_ID', 'CacheId', 'INTEGER', 'cache', 'ID', false, null, null);
$this->addForeignKey('TAG_ID', 'TagId', 'INTEGER', 'tag', 'ID', false, null, null);
// validators
} // initialize()
/**
* Build the RelationMap objects for this table relationships
*/
public function buildRelations()
{
$this->addRelation('Cache', 'Cache', RelationMap::MANY_TO_ONE, array('cache_id' => 'id', ), null, null);
$this->addRelation('Tag', 'Tag', RelationMap::MANY_TO_ONE, array('tag_id' => 'id', ), null, null);
} // buildRelations()
} // CacheTagMmTableMap
Exception Backtrace:
13 TableMap::getColumn("cache_id")
12 TableMap::addRelation("CacheTagMm", "CacheTagMm", 2, array, NULL, NULL, "CacheTagMms")
11 CacheTableMap::buildRelations()
10 TableMap::getRelations()
9 TableMap::getRelation("CacheTagMm")
8 BaseCacheQuery::joinCacheTagMm(NULL, "LEFT JOIN")
7 BaseCacheQuery::useCacheTagMmQuery()
Digging further into the TableMaps I figured out that each (Tag, Cache, CacheTagMm) built buildRelations() function contains wrong code. I edited those parts by doing a strtoupper() on the column mapping array passed as 4th argument. CacheTableMap example:
public function buildRelations()
{
$this->addRelation('Cache', 'Cache', RelationMap::MANY_TO_ONE, array('CACHE_ID' => 'ID', ), null, null);
$this->addRelation('Tag', 'Tag', RelationMap::MANY_TO_ONE, array('TAG_ID' => 'ID', ), null, null);
} // buildRelations()
This fixed the problem! However i don't understand why propel builds the files like that. Is there an error in my schema.xml? Some problem with the default naming method? Only Problem is that i have to update those files each time i edited my schema and rebuilt the project. I downgraded phing version to 2.4.5 (as mentioned as min version in the documentation), but that didn't change anything. Any hints?
Upvotes: 1
Views: 390
Reputation: 2100
I would really recommend against altering the generated files in propel or you're going to have a real headache when you change something in the schema. The change to the tablenames is something that propel does automatically, but unless you altered something in the generated text originally, then there shouldn't have been an error thrown by the column map, and when you built the propel it should have thrown an error if there was a problem with the relationships you're defining.
You may want to try generating a fresh Propel directory, and freshly generated files and drop them over what you have, then see if you still get the error with your relationship. Just do a very simple query like
$collection = CacheQuery()::create()
->limit(1)
->findOne();
$relationshipTest = $collection->getTag();
And see if you still get an error. Once you start renaming stuff inside the generated files you're going to be in a bad way.
If you want to override propel's default naming conventions, you can use the phpname attribute in your schema, and this will override propel's defaults with whatever you like (in case you want to preserve your underscore)
Upvotes: 1