The Project object validates successfully and saves. But the Budget object is invalid and should not be saved. In this circumstance, I want the Project to be considered invalid as well and NOT be saved.
I tried adding a validation hook to Project that makes it fail validation if !$this->Budgets[0]->isValid(). But the problem then is that $project always fails validation, because $project->Budgets[0]->project_id will never be set at this stage in the save operation.
Is there a way to make this work? I fear part of the problem may be that I'm using MyISAM and not InnoDB tables, which means I don't get the benefit of transactions and true foreign keys. I hope this makes sense...it's a bit convoluted.
I did a little more testing on this. If I change the table types to InnoDB, then the Project insertion is correctly rolled back when the Budget save fails.
HOWEVER, the Project's postInsert() is called, which is problematic. In this particular case, the addition of a Project record triggers an email notification, which doesn't make sense if the record hasn't actually been persisted to the database. Shouldn't postInsert() only run if the insertion is totally successful?
_____
From: doctrine-user@googlegroups.com [mailto:doctrine-user@googlegroups.com] On Behalf Of Adam Huttler Sent: Saturday, July 05, 2008 11:05 AM To: doctrine-u...@groups.google.com Subject: [doctrine-user] validation with compound inserts
Hi,
I must be doing something stupid, because I know Doctrine can support this, but I can't get it working.
Let's say I have a class called Project and that each Project has many Budgets. A Budget can't exist without an associated Project.
Let's also say that Project has a required field called "name" and that Budget has a required field called "income".
I've got a form that populates a new Project along with its initial Budget.
The Project object validates successfully and saves. But the Budget object is invalid and should not be saved. In this circumstance, I want the Project to be considered invalid as well and NOT be saved.
I tried adding a validation hook to Project that makes it fail validation if !$this->Budgets[0]->isValid(). But the problem then is that $project always fails validation, because $project->Budgets[0]->project_id will never be set at this stage in the save operation.
Is there a way to make this work? I fear part of the problem may be that I'm using MyISAM and not InnoDB tables, which means I don't get the benefit of transactions and true foreign keys. I hope this makes sense...it's a bit convoluted.
Okay, so I'm having a little dialogue with myself on the list... (Sorry about that.)
But I've dug into the Doctrine code and I think I understand what's happening. Because the Budget object has the foreign key for the Project object, it is being added to $saveLater when running UnitOfWork::saveRelated($project). If it was a local key, then (I think) the behavior would work as anticipated.
What is the rationale for treating local key relations and foreign key relations differently in this respect? Is the logic that the Budget is non-essential to the Project, therefore it shouldn't interfere with the Project being saved? But this creates the decidedly counterintuitive behavior described below.
_____
From: doctrine-user@googlegroups.com [mailto:doctrine-user@googlegroups.com] On Behalf Of Adam Huttler Sent: Saturday, July 05, 2008 12:29 PM To: doctrine-user@googlegroups.com Subject: [doctrine-user] Re: validation with compound inserts
I did a little more testing on this. If I change the table types to InnoDB, then the Project insertion is correctly rolled back when the Budget save fails.
HOWEVER, the Project's postInsert() is called, which is problematic. In this particular case, the addition of a Project record triggers an email notification, which doesn't make sense if the record hasn't actually been persisted to the database. Shouldn't postInsert() only run if the insertion is totally successful?
_____
From: doctrine-user@googlegroups.com [mailto:doctrine-user@googlegroups.com] On Behalf Of Adam Huttler Sent: Saturday, July 05, 2008 11:05 AM To: doctrine-u...@groups.google.com Subject: [doctrine-user] validation with compound inserts
Hi,
I must be doing something stupid, because I know Doctrine can support this, but I can't get it working.
Let's say I have a class called Project and that each Project has many Budgets. A Budget can't exist without an associated Project.
Let's also say that Project has a required field called "name" and that Budget has a required field called "income".
I've got a form that populates a new Project along with its initial Budget.
The Project object validates successfully and saves. But the Budget object is invalid and should not be saved. In this circumstance, I want the Project to be considered invalid as well and NOT be saved.
I tried adding a validation hook to Project that makes it fail validation if !$this->Budgets[0]->isValid(). But the problem then is that $project always fails validation, because $project->Budgets[0]->project_id will never be set at this stage in the save operation.
Is there a way to make this work? I fear part of the problem may be that I'm using MyISAM and not InnoDB tables, which means I don't get the benefit of transactions and true foreign keys. I hope this makes sense...it's a bit convoluted.
On Jul 5, 7:47 pm, "Adam Huttler" <adam.hutt...@fracturedatlas.org>
wrote:
> But I've dug into the Doctrine code and I think I understand what's
> happening. Because the Budget object has the foreign key for the Project
> object, it is being added to $saveLater when running
> UnitOfWork::saveRelated($project). If it was a local key, then (I think)
> the behavior would work as anticipated.
> What is the rationale for treating local key relations and foreign key
> relations differently in this respect? Is the logic that the Budget is
> non-essential to the Project, therefore it shouldn't interfere with the
> Project being saved? But this creates the decidedly counterintuitive
> behavior described below.
When inside saveRelated():
A ForeignKey relation means the other (related) record/entity owns the
(database) foreign key.
A LocalKey relation means *this* record/entity owns the (database)
foreign key and the related one does not.
Therefore, the save() operation is forwarded to LocalKey related
entities/records immediately whilst ForeignKey related records/
entities are "saved later". It's a matter of maintaining referential
integrity. The inverse side (without the foreign key) needs to be
saved first so that we can grab its (often database generated)
identifier and store it in the foreign key field.
> From: doctrine-user@googlegroups.com
> [mailto:doctrine-user@googlegroups.com] On Behalf Of Adam Huttler
> Sent: Saturday, July 05, 2008 12:29 PM
> To: doctrine-user@googlegroups.com
> Subject: [doctrine-user] Re: validation with compound inserts
> I did a little more testing on this. If I change the table types to
> InnoDB, then the Project insertion is correctly rolled back when the
> Budget save fails.
> HOWEVER, the Project's postInsert() is called, which is problematic. In
> this particular case, the addition of a Project record triggers an email
> notification, which doesn't make sense if the record hasn't actually been
> persisted to the database. Shouldn't postInsert() only run if the
> insertion is totally successful?
We had a discussion about this before. I'm really not sure what the
best behavior is in such cases generally. I tend to agree with you.
But your particular case rather points out a known fundamental issue
with the validation system. Validation on the object graph should
happen completely before any database operations. Then, obviously, any
postInsert listeners will not be invoked and you're also not dependant
upon a database rollback to roll back partial changes because of a
validation error. I will address this in the validator rewrite for 2.0
which will be based on event listeners and loosely coupled with
Doctrine. I don't think we can get this corrected before 1.0 for bc
reasons and simply because its currently so hard-wired into Doctrines
innards which makes it hard and risky to rip it out there.
> When inside saveRelated(): > A ForeignKey relation means the other (related) record/entity owns the > (database) foreign key. > A LocalKey relation means *this* record/entity owns the (database) > foreign key and the related one does not.
> Therefore, the save() operation is forwarded to LocalKey related > entities/records immediately whilst ForeignKey related records/ > entities are "saved later". It's a matter of maintaining referential > integrity. The inverse side (without the foreign key) needs to be > saved first so that we can grab its (often database generated) > identifier and store it in the foreign key field.
Thanks for the reply. That makes sense. But do you agree that it's a problem that postInsert() is run on an object that isn't actually persisted to the database?
IMO that needs to be fixed. I realize that would be a non-trivial refactoring, but perhaps during the save operation we could create a stack of inserted/updated records and then after the commit run all the listeners in the correct order. What do you think?
On Jul 6, 2:48 pm, "Adam Huttler" <adam.hutt...@fracturedatlas.org>
wrote:
> Thanks for the reply. That makes sense. But do you agree that it's a
> problem that postInsert() is run on an object that isn't actually
> persisted to the database?
> IMO that needs to be fixed. I realize that would be a non-trivial
> refactoring, but perhaps during the save operation we could create a stack
> of inserted/updated records and then after the commit run all the
> listeners in the correct order. What do you think?
> Thanks,
> Adam
I'm not absolutely sure to be honest but I think the listeners should
always be invoked when the program flow continues normally regardless
of the context (no "if (foo) dispatch event"). Of course, if an
exception is thrown thats a different matter and thats actually the
problem in your case. It is the same problem I already pointed out:
The validation is hard-wired inmidden of the persisting procedure.
Ideally your situation should work like this:
1) you save the project
2) the project and the budget get validated BEFORE INSERT
3) when there are validation errors throw an exception ---> out,
execution goes to your code to an appropriate catch block
(4) persist the graph, dipatching post* events)
But currently it looks like this (i guess):
1) you save the project
2) project gets validated -> preInsert() -> insert project into db ->
postInsert()
3) budget gets validated -> validation errors -> exception
I see 2 reasons for not doing conditional event dispatching from
within Doctrine:
1) Its much more predictable, you can be assured that listeners are
always invoked except if an exception is thrown. If a lot of listeners
start to have some other conditions on when to dispatch the event this
becomes quite irritating and needs extra documentation for all the
special cases.
2) It's relatively easy to check in the post* whether you're in a
valid condition for your custom logic. Not dispatching the event would
mean preventing certain usage scenarios from being possible to
implement at all. I'm sure there are quite some scenarios where you
want to cancel the actual event but want your post* handler to run.
Last but not least i've checked other orms (hibernate in this case)
and it looks like this:
...
boolean veto = preInsert(); // invoked all preInsert listeners
if ( !veto ) {
generatedId = persister.insert( state, instance, session );
if ( persister.hasInsertGeneratedProperties() ) {
persister.processInsertGeneratedProperties( generatedId, instance,
state, session );
}
//need to do that here rather than in the save event listener to
let
//the post insert events to have a id-filled entity when IDENTITY
is used (EJB3)
persister.setIdentifier( instance, generatedId,
session.getEntityMode() );
}
postInsert(); // invokes all postinsert listeners
...
That makes me even more confident that it is the right choice.
If you still feel otherwise feel free to open a ticket where you state
your arguments and we will put this to a broader discussion.
> Ideally your situation should work like this: > 1) you save the project > 2) the project and the budget get validated BEFORE INSERT > 3) when there are validation errors throw an exception ---> out, > execution goes to your code to an appropriate catch block > (4) persist the graph, dipatching post* events)
This is what I tried to do originally. But the problem is that the Budget won't validate until after the Project has been inserted because it needs that foreign key. Can you think of a way to work around this?
The only thing I can think of would be a rather gross hack... I could set the budget's project_id to some kind of "stand in" temporary value, just to see if it can validate otherwise, and then manually set it to the correct id after the project is inserted. But that just seems very wrong somehow.
Thanks for your help.
Adam
PS - I understand your reasoning, but I still think it's counterintuitive for postInsert() to be called when the insertion doesn't actually complete. A Doctrine_Validator_Exception is thrown within the context of the save operation, and the transaction is rolled back, but it's after postInsert() has been called. I'll add a ticket and we can see what others think.
On Jul 6, 7:51 pm, "Adam Huttler" <adam.hutt...@fracturedatlas.org>
wrote:
> Hi Roman,
> Thanks for the thoughtful reply.
> > Ideally your situation should work like this:
> > 1) you save the project
> > 2) the project and the budget get validated BEFORE INSERT
> > 3) when there are validation errors throw an exception ---> out,
> > execution goes to your code to an appropriate catch block
> > (4) persist the graph, dipatching post* events)
> This is what I tried to do originally. But the problem is that the Budget
> won't validate until after the Project has been inserted because it needs
> that foreign key. Can you think of a way to work around this?
Right. The problem is that the steps i described above are not what is
happening. Its just how it *should* be in Doctrine.
> The only thing I can think of would be a rather gross hack... I could set
> the budget's project_id to some kind of "stand in" temporary value, just
> to see if it can validate otherwise, and then manually set it to the
> correct id after the project is inserted. But that just seems very wrong
> somehow.
> Thanks for your help.
> Adam
> PS - I understand your reasoning, but I still think it's counterintuitive
> for postInsert() to be called when the insertion doesn't actually
> complete. A Doctrine_Validator_Exception is thrown within the context of
> the save operation, and the transaction is rolled back, but it's after
> postInsert() has been called. I'll add a ticket and we can see what others
> think.
Yes, no question about that. As i said, the validation should happen
completely before any database operations. 1) validate the graph 2)
save the graph. And in your case step 1 should throw the exception and
therefore postInsert events would never occur for any of your records/
entities.
Unfortunately, thats just not the way it works currently as validation
& insertion are intertwined with one another.
Please add a link to this thread to the ticket for reference. I cant
think of any half-way decent workaround from the user perspective
currently. This is something that needs to be fixed in the core im
just not sure we can make ths prior to 1.0. Would also be good to hear
Jonathans opinion on this.
One option i can think of would be to pull the code in postInsert()
into a separate method in your domain object and call this method
(which sends an email) manually from your code if no validation errors
occur. Of course this is just a workaround.
On Jul 6, 8:07 pm, romanb <ro...@code-factory.org> wrote:
> > > Ideally your situation should work like this:
> > > 1) you save the project
> > > 2) the project and the budget get validated BEFORE INSERT
> > > 3) when there are validation errors throw an exception ---> out,
> > > execution goes to your code to an appropriate catch block
> > > (4) persist the graph, dipatching post* events)
> > This is what I tried to do originally. But the problem is that the Budget
> > won't validate until after the Project has been inserted because it needs
> > that foreign key. Can you think of a way to work around this?
> Right. The problem is that the steps i described above are not what is
> happening. Its just how it *should* be in Doctrine.
> > The only thing I can think of would be a rather gross hack... I could set
> > the budget's project_id to some kind of "stand in" temporary value, just
> > to see if it can validate otherwise, and then manually set it to the
> > correct id after the project is inserted. But that just seems very wrong
> > somehow.
> > Thanks for your help.
> > Adam
> > PS - I understand your reasoning, but I still think it's counterintuitive
> > for postInsert() to be called when the insertion doesn't actually
> > complete. A Doctrine_Validator_Exception is thrown within the context of
> > the save operation, and the transaction is rolled back, but it's after
> > postInsert() has been called. I'll add a ticket and we can see what others
> > think.
> Yes, no question about that. As i said, the validation should happen
> completely before any database operations. 1) validate the graph 2)
> save the graph. And in your case step 1 should throw the exception and
> therefore postInsert events would never occur for any of your records/
> entities.
> Unfortunately, thats just not the way it works currently as validation
> & insertion are intertwined with one another.
> Please add a link to this thread to the ticket for reference. I cant
> think of any half-way decent workaround from the user perspective
> currently. This is something that needs to be fixed in the core im
> just not sure we can make ths prior to 1.0. Would also be good to hear
> Jonathans opinion on this.
That would work, but I'm really anal about keeping model logic in the model class (and not in the controller).
I just figured out something else that seems to work, but it's a pretty ugly hack:
- I removed the notnull constraint from the Budget table definition (though it still exists in the database).
- The Project's validateOnInsert() checks to see if Budgets[0]->isValid(). If not, it throws a Doctrine_Validator_Exception.
- The Budget's preInsert() checks to make sure project_id is set and (since at this point it will actually be a Project object rather than an id) that project_id->id is set. If not, it throws a Doctrine_Validator_Exception.
This is pretty nasty, and is totally dependent on the way Doctrine's internals work right now. But it seems like it should work for the time being (unless you can see something wrong here?)
I do think going forward it would be great if we could come up with a real solution.
> -----Original Message----- > From: doctrine-user@googlegroups.com > [mailto:doctrine-user@googlegroups.com] On Behalf Of romanb > Sent: Sunday, July 06, 2008 2:22 PM > To: doctrine-user > Subject: [doctrine-user] Re: validation with compound inserts
> One option i can think of would be to pull the code in postInsert() > into a separate method in your domain object and call this method > (which sends an email) manually from your code if no validation errors > occur. Of course this is just a workaround.
> > > > Ideally your situation should work like this: > > > > 1) you save the project > > > > 2) the project and the budget get validated BEFORE INSERT > > > > 3) when there are validation errors throw an exception > ---> out, > > > > execution goes to your code to an appropriate catch block > > > > (4) persist the graph, dipatching post* events)
> > > This is what I tried to do originally. But the problem is > that the Budget > > > won't validate until after the Project has been inserted > because it needs > > > that foreign key. Can you think of a way to work around this?
> > Right. The problem is that the steps i described above are > not what is > > happening. Its just how it *should* be in Doctrine.
> > > The only thing I can think of would be a rather gross > hack... I could set > > > the budget's project_id to some kind of "stand in" > temporary value, just > > > to see if it can validate otherwise, and then manually > set it to the > > > correct id after the project is inserted. But that just > seems very wrong > > > somehow.
> > > Thanks for your help.
> > > Adam
> > > PS - I understand your reasoning, but I still think it's > counterintuitive > > > for postInsert() to be called when the insertion doesn't actually > > > complete. A Doctrine_Validator_Exception is thrown within > the context of > > > the save operation, and the transaction is rolled back, > but it's after > > > postInsert() has been called. I'll add a ticket and we > can see what others > > > think.
> > Yes, no question about that. As i said, the validation should happen > > completely before any database operations. 1) validate the graph 2) > > save the graph. And in your case step 1 should throw the > exception and > > therefore postInsert events would never occur for any of > your records/ > > entities. > > Unfortunately, thats just not the way it works currently as > validation > > & insertion are intertwined with one another.
> > Please add a link to this thread to the ticket for reference. I cant > > think of any half-way decent workaround from the user perspective > > currently. This is something that needs to be fixed in the core im > > just not sure we can make ths prior to 1.0. Would also be > good to hear > > Jonathans opinion on this.