Let's go:
Here, the aim is to create a "Commentable" behavior to allow us to say that a class can have some comments and also create the corresponding comment class. In the exemple I suppose I need to add some comments on a Post class and a comment have the following fields: content, element_id, user_id and created_at. I would like to write my schema.yml like this: (and also be able to define the name of my user class to create a foreign key on the user_id field):

#config/doctrine/schema.yml
Post:
  actAs:
    Timestampable: ~
    Commentable:
      userColumn: MyUserClass
  columns:
    content: { type: string }

MyUserClass:
  actAs:
    Timestampable: ~
  columns:
    name: { type: string }

Let's start to create our behavior, first of all we need to create a template class to define fields and relations that a class on which we apply the template will have. In our exemple this template will be used on the Post class. So let's create the Commentable template to define that the Post class has many comments. To do this template simply create a new class (for exemple in my_project/lib/template/) and extend the Doctrine_Template class. We will have to use two functions:

  • setTableDefinition(): define the table fields
  • setUp(): define the relations and load generators.
// lib/template/Commentable.class.php
class Commentable extends Doctrine_Template
{
    public function setTableDefinition() { }

    public function setUp()
    {
        $this->hasMany($this->getTable()->getComponentName().'Comment as Comments', // here $this->getTable()->getComponentName() will return 'Post'
                       array('local' => 'id',
                             'foreign' => 'element_id'));
    }
}

Ok so we have a temple to say that a commentable class has many comments, and now we need a comment class (it will be PostComment in the exemple), and we will also generate files for this class. So to do that we can create a generator. The generator class will define fields and relations for the comment class (PostComment) and generate the files if needed. Let's create a CommentGenerator class (in my_project/lib/generator/) which extend the Doctrine_Record_Generator class.

// lib/generator/CommentGenerator.class.php
class CommentGenerator extends Doctrine_Record_Generator
{
    public function initOptions()
    {
        $builderOptions = array('suffix' => '.class.php',
                                'baseClassesDirectory' => 'base',
                                'generateBaseClasses' => true,
                                'generateTableClasses' => true,
                                'baseClassName' => 'sfDoctrineRecord');

        $this->setOption('builderOptions', $builderOptions);
        $this->setOption('className', '%CLASS%Comment');
        $this->setOption('generateFiles', true);
        $this->setOption('generatePath', sfConfig::get('sf_lib_dir').DIRECTORY_SEPARATOR.'model'.DIRECTORY_SEPARATOR.'doctrine');
    }

    public function getRelationLocalKey()
    {
        return 'element_id';
    }

    public function buildRelation()
    {
        $this->buildForeignRelation('Comments');
        $this->buildLocalRelation('Element');
    }

    public function setTableDefinition()
    {
        $this->hasColumn('id', 'integer', null, array('primary' => true, 'autoincrement' => true));
        $this->hasColumn('content', 'text', null, array('type' => 'text'));
        $this->hasColumn($this->getRelationLocalKey(), 'integer');
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('created_at', 'date');
    }

    public function setUp()
    {
        $this->hasOne($this->getOption('userClass'),
                      array('local' => 'user_id',
                            'foreign' => 'id',
                            'onDelete' => 'cascade'));
    }
}

Now we will add a listener to automaticly handle the created_at field on an insertion. Of course we could use the Timesptampable behavior in the table definition of CommentGenerator, but for the exemple I prefer create a new listener. So for this listener, we just need to create a CommentListener which extend from Doctrine_Record_Listener and overrride some function such as preInsert() in our case.

// lib/listener/CommentListener.class.php
class CommentListener extends Doctrine_Record_Listener
{
    public function preInsert(Doctrine_Event $event)
    {
        $event->getInvoker()->created_at = date('Y-m-d', time());
    }
}

Add then we can add the listener to our generator class in setTableDefinition(). Here I also add a constructor to get the options from the template .

// lib/generator/CommentGenerator.class.php
class CommentGenerator extends Doctrine_Record_Generator
{
    public function __construct($options)
    {
        $this->addOptions($options);
    }

    public function initOptions()
    {
        $builderOptions = array('suffix' => '.class.php',
                                'baseClassesDirectory' => 'base',
                                'generateBaseClasses' => true,
                                'generateTableClasses' => true,
                                'baseClassName' => 'sfDoctrineRecord');

        $this->setOption('builderOptions', $builderOptions);
        $this->setOption('className', '%CLASS%Comment');
        $this->setOption('generateFiles', true);
        $this->setOption('generatePath', sfConfig::get('sf_lib_dir').DIRECTORY_SEPARATOR.'model'.DIRECTORY_SEPARATOR.'doctrine');
    }

    public function getRelationLocalKey()
    {
        return 'element_id';
    }

    public function buildRelation()
    {
        $this->buildForeignRelation('Comments');
        $this->buildLocalRelation('Element');
    }

    public function setTableDefinition()
    {
        $this->hasColumn('id', 'integer', null, array('primary' => true, 'autoincrement' => true));
        $this->hasColumn('content', 'text', null, array('type' => 'text'));
        $this->hasColumn($this->getRelationLocalKey(), 'integer');
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('created', 'date');

        $this->addListener(new CommentListener());
    }

    public function setUp()
    {
        $this->hasOne($this->getOption('userClass'),
                      array('local' => 'user_id',
                            'foreign' => 'id',
                            'onDelete' => 'cascade'));
    }

    // I override this function and set the generate_once option to true.
    // The file seems to be generated each time the behavior is set up, so if you don't generate the file you don't need this, the class will be loaded in memory and nothing is write physically.
    public function generateClassFromTable(Doctrine_Table $table) 
    {
        $definition = array();
        $definition['columns'] = $table->getColumns();
        $definition['tableName'] = $table->getTableName();
        $definition['actAs'] = $table->getTemplates();
        $definition['generate_once'] = true;

        return $this->generateClass($definition);
    }

    public function addOptions(array $options)
    {
        $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
    }
}

So now in the Commentable template we just need to load the CommentGenerator as a template's plugin (the Doctrine_Template class also provide the loadGenerator() method).

// lib/template/Commentable.class.php
class Commentable extends Doctrine_Template
{
    public function __construct(array $options = array())
    {
        parent::__construct($options);

        if($this->getOption('userClass', null) == null)
        {
            throw new sfException('You need to specify the userClass option for Commentable.');
        }

        $this->_plugin = new CommentGenerator($this->getOptions());
    }

    public function setTableDefinition() { }

    public function setUp()
    {
        $this->hasMany($this->getTable()->getComponentName().'Comment as Comments',
                       array('local' => 'id',
                             'foreign' => 'element_id'));

        $this->_plugin->initialize($this->getTable());
    }
}

Now just run a build-model and a build-sql and then look at the generated files and sql.
If you choose to generate files for PostComment you can also build forms and filters, and symfony you sould create the corresponding form and form filter classes.