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?
This is not a bug. 🙂
The problem here lies in the auditlog_id field in the AuditLogProperty model.
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.