I've run into a bit of a snag in my application where a relationship defined as a one-to-many relationship returns a model object (instance of Doctrine_Record) instead of a Doctrine_Collection when I try to access it as $model->RelatedComponent[] = $child1. This, of course, yields an exception like so:

Doctrine_Exception: Add is not supported for AuditLogProperty

#0 path\library\Doctrine\Access.php(131): Doctrine_Access->add(Object(AuditLogProperty))

#1 path\application\models\Article.php(58): Doctrine_Access->offsetSet(NULL, Object(AuditLogProperty))

#2 path\library\Doctrine\Record.php(354): Article->postInsert(Object(Doctrine_Event))

#3 path\library\Doctrine\Connection\UnitOfWork.php(576): Doctrine_Record->invokeSaveHooks('post', 'insert', Object(Doctrine_Event))

#4 path\library\Doctrine\Connection\UnitOfWork.php(81): Doctrine_Connection_UnitOfWork->insert(Object(Article))

#5 path\library\Doctrine\Record.php(1718): Doctrine_Connection_UnitOfWork->saveGraph(Object(Article))

#6 path\application\modules\my-page\controllers\ArticleController.php(26): Doctrine_Record->save()

#7 path\library\Zend\Controller\Action.php(513): MyPage_ArticleController->createAction()

#8 path\library\Zend\Controller\Dispatcher\Standard.php(289): Zend_Controller_Action->dispatch('createAction')

#9 path\library\Zend\Controller\Front.php(946): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))

#10 path\library\Zend\Application\Bootstrap\Bootstrap.php(77): Zend_Controller_Front->dispatch()

#11 path\library\Zend\Application.php(358): Zend_Application_Bootstrap_Bootstrap->run()

#12 path\public\index.php(11): Zend_Application->run()

#13 {main}

This is what my yaml-schema looks like (excerpt):

AuditLogEntry:
  tableName: audit_log_entries
  actAs:
    Timestampable:
      updated: {disabled: true}
  columns:
    user_id: {type: integer(8), unsigned: true, primary: true}
    id: {type: integer(8), unsigned: true, primary: true, autoincrement: true}
    type: {type: string(255), notnull: true}
    mode: {type: string(16)}
    article_id: {type: integer(8), unsigned: true}
    comment_id: {type: integer(8), unsigned: true}
    question_id: {type: integer(8), unsigned: true}
    answer_id: {type: integer(8), unsigned: true}
    message_id: {type: integer(8), unsigned: true}
  indexes:
#   Must index autoincrementing id-column since it's a compound primary key and 
#   the auto-incrementing column is not the first column and we use InnoDB.
    id: {fields: [id]}
    type: {fields: [type, mode]}
  relations:
    User:
      local: user_id
      foreign: user_id
      foreignAlias: AuditLogs
      type: one
      onDelete: CASCADE
      onUpdate: CASCADE

And then we have the related model:

AuditLogProperty:
  tableName: audit_log_properties
  columns:
    auditlog_id: {type: integer(8), unsigned: true, primary: true}
    prop_id: {type: integer(2), unsigned: true, primary: true, default: 1}
    name: {type: string(255), notnull: true}
    value: {type: string(1024)}
  relations:
    AuditLogEntry:
      local: auditlog_id
      foreign: id
      type: one
      foreignType: many
      foreignAlias: Properties
      onDelete: CASCADE
      onUpdate: CASCADE

Now, if we look at the generated class-files, it looks fine:

/**
 * @property integer $user_id
 * @property integer $id
 * @property string $type
 * @property string $mode
 * @property integer $article_id
 * @property integer $comment_id
 * @property integer $question_id
 * @property integer $answer_id
 * @property integer $message_id
 * @property integer $news_comment_id
 * @property User $User
 * @property Doctrine_Collection $Properties
 * @property Doctrine_Collection $Notifications
 */
abstract class BaseAuditLogEntry extends Doctrine_Record

/**
 * @property integer $auditlog_id
 * @property integer $prop_id
 * @property string $name
 * @property string $value
 * @property AuditLogEntry $AuditLogEntry
 */
abstract class BaseAuditLogProperty extends Doctrine_Record

However, when I later try to add properties I get the exception posted in the beginning of the question:

$auditLog = new AuditLogEntry();
$prop1 = new AuditLogProperty();
$prop1->name = 'title';
$prop1->value = $this->Content->title;
$prop2 = new AuditLogProperty();
$prop2->name = 'length';
$prop2->value = count($this->Content->plainText);
$auditLog->Properties[] = $prop1;
$auditLog->Properties[] = $prop2;
$auditLog->save();

If I do the following:

var_dump(get_class($auditLog->Properties));

I get that Properties is of type AuditLogProperty, instead of Doctrine_Collection.

I use version 1.2.3 of Doctrine.

  • Can anyone spot what is wrong?
  • Is the problem that I use compound primary keys in a way Doctrine doesn't approve of?
  • Any ideas of how to solve it?

Comments

I'm having the same problem. Issue exists in Doctrine 1.2.X (I tested previous versions).

Written by Joshua Johnson

I've filed a bug on doctrine-project: doctrine-project.org/jira/browse/DC-875

Written by PatrikAkerstrand

Accepted Answer

This is not a bug. :)

The problem here lies in the auditlog_id field in the AuditLogProperty model.

  1. This field is a primary key
  2. This field is also a foreign key
  3. Condition 1 and 2 can not apply at the same time.

EDIT: This varies per database type, but Doctrine does not (seem to) allow it.

As soon as you remove the 'primary: true' from the auditlog_id field in your AuditLogProperty model and recreate the database, you will see that your problem disappears.

I don't know exactly why this is impossible, but I could never recommend to make a foreign key primary as well (unless it happens inside a reference table in a many-to-many relation). If you need a FK to be unique, use 'unique:true' instead.

I do understand though that you want to have one combination of prop_id and auditlog_id only once. As far as I know, the only way to achieve this in Doctrine is by using business rules in your model. You can for instance implement public function preInsert($event) inside your PHP class that makes the check and throws an exception (Doctrine would do the same) when something is wrong.

Written by Pelle ten Cate
This page was build to provide you fast access to the question and the direct accepted answer.
The content is written by members of the stackoverflow.com community.
It is licensed under cc-wiki