mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
DDC-79: Allow constructor arguments in persistable models #93
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @doctrinebot on GitHub (Oct 30, 2009).
Jira issue originally created by user dennari:
Currently, at the reconstitution phase, models are created using the 'new' keyword. This implies a requirement that one must be able to create the model without any arguments to it's constructor. Actually this is the only requirement for the models implied by Doctrine. This requirement makes it impossible to use constructor-based dependency injection for example.
At least two project's that I know of (php-object-freezer and FLOW3) solve this by using a trick which creates an object without running it's constructor:
$object = unserialize(sprintf('O:%d:"%s":0:{}', strlen($className), $className));
@doctrinebot commented on GitHub (Oct 30, 2009):
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by romanb:
This looks like a rather slow workaround to me. You can use constructor arguments but they must all be optional.
The "restrictions" on entities are explained here:
http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by romanb:
I will try the alternative, however, and see how it performs/works, thanks.
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by romanb:
Here is a small test of mine:
Results:
So its twice as slow. However, this is for 100000 objects. So in practice this might not make any noticeable difference. I will keep this open for discussion. Thanks for the suggestion.
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by dennari:
It would seem to me that the advantages outweigh the slight performance cost. I'd thought that the difference was more.
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by romanb:
Yes, I thought it would be slower, too.
I have yet another idea, that might even be faster than "new ..". We could store a prototype in ClassMetadata like so:
Then using:
Clone is even faster than new. In the test above, the result of using clone is: 0.065 seconds
Only drawback: Entity classes must not implement **clone
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by dennari:
Well, I'd much rather take constructors with no requirements than no **clone. I made another, more 'real life', test:
In UnitOfWork->createEntity I change between
Test:
Results (reconstitution times):
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by romanb:
5000 find() == 5000 database queries is not so much a real-life test, hehe.
"Well, I'd much rather take constructors with no requirements than no **clone"
I dont understand. Would you be ok with the cloning solution or not?
@doctrinebot commented on GitHub (Oct 30, 2009):
Comment created by dennari:
Sorry if I was unclear. To me the cloning solution is absolutely perfect, as I've never even used **clone. But I can guess that for some it might be a big deal. I'd suggest that by default Doctrine would use unserialize(...) every time, as this would be the most 'compatible' solution. Using the cloning strategy could be a config option.
p.s. Okay, maybe not exactly real life :) .
@doctrinebot commented on GitHub (Oct 31, 2009):
Comment created by @beberlei:
The unserialize hack begs the question if there is a PHP Unit-Test for it. Its so ugly I wouldn't rely on it working across all php versions in the past and future :-)
@doctrinebot commented on GitHub (Oct 31, 2009):
Comment created by romanb:
@Benjamin: I think the fact that unserialization does not invoke the constructor is by design. You would not expect that that constructor gets invoked again whenever an object is serialized/unserialized. This behavior is also found in many other languages, at least for Java and C# I can say its pretty much the same. So I think we could rely on it to work.
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by dennari:
Yes, it's certainly not by chance that the constructor doesn't get called. It would complicate object serialization/unserialization quite a bit.
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by @beberlei:
Both unserialize and clone have domain use-cases. I am not convinced, maybe make the three options configurable?
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by dennari:
Making it configurable is of course a good idea. Benjamin, this wouldn't affect any domain use-cases in any way, on the contrary it gives back all the use-cases that a constructor might have. Only drawback is the performance penalty that is measured here. Just because it's a little ugly doesn't mean it shouldn't be used :)
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by romanb:
Having configuration options for something like this is something I'd like to avoid. The problem is that all these options: constructor, clone and unserialization have domain use-cases. And I have to agree that the constructor is probably the most intrusive.
@Ville: What Benjamin means is that whenever unserialize() is called and the domain class has either a **wakeup method or implements the Serializable interface, then this method will be called.
This is really difficult.
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by dennari:
Yes of course, overlooked the __wakeup call... Although if you think about it, unserialization of and reconstitution of an object by Doctrine are not entirely different types of events. Getting a __wakeup call might even be useful.
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by dennari:
Constructor arguments are a great way of enforcing required dependencies and I'd go as far as to say that having that option when designing your models is quite a big deal. And to be clear, this is not something I came up with myself, I first saw it in Sebastian Bergmann's presentation Cool Objects Sleep on the Couch (p.29 and **wakeup is mentioned also) and later on found out that the FLOW3 library is using this method as well.
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by romanb:
I got some interesting new findings.
Apparently, if a class implements Serializable, Serializable#unserialize() is not called when unserializing directly from such a compact string as shown in the issue description. This means only __wakeup is called. But since __wakeup is not really needed and the "OO-way" is to implement Serializable I think this is fine. Concerning cloning, I think this is very rarely used and can always be done in other ways by just implementing a regular ->clone/->copy method that acts according to the desired behavior.
Given this new fact that serializing/unserializing through the Serializable interface is not affected by this special unserialize operation I am starting to lean towards the special unserialize/clone combination.
Can someone confirm that "unserialize(sprintf('O:%d:"%s":0:{}', strlen($className), $className));" does not invoke unserialize() if $className implements Serializable?
@doctrinebot commented on GitHub (Nov 2, 2009):
Comment created by @beberlei:
confirms it.
@doctrinebot commented on GitHub (Dec 9, 2009):
Comment created by @beberlei:
I have some cons on this:
@doctrinebot commented on GitHub (Dec 9, 2009):
Comment created by romanb:
These are actually both Pros.
The constructor is not supposed to be executed when a persisted object is queried for. It exists already, it was created before. It is very counter-intuitive that it is constructed again.
Imagine that what we're actually trying to do is to make it look as if the persisted objects never leave memory.
Logic and initializations in the constructor additionally just slow down the hydration/reconstitution process.
@doctrinebot commented on GitHub (Dec 9, 2009):
Comment created by romanb:
Either way is surely not perfect but I feel not calling the constructor when reconstituting already persistent objects as preferrable.
Some object databases and ORMs have a configuration switch for this, but I am really not keen on making configuration checks in such critical code paths.
PostLoad is a good callback for initializations of transient/non-persistent fields in an entity that should be initialized after reconstitution also.
Probably like this:
Again, I think there is no perfect approach to this, but I like this one better.
@doctrinebot commented on GitHub (Dec 9, 2009):
Comment created by romanb:
To show some problems with calling the constructor on reconstitution:
Currently this would cause real issues because the elements would be contained in collections of reconstituted entities.
Of course we could empty out the collections when the object is being reconstituted but in that case it would still be
code that is executed unnecessarily. Objects created and destroyed for nothing.
Bottom line, we just cant know which of the logic in a constructor is supposed or safe to be executed on reconstitution.
Just to show some more dangerous things of the current approach (in addition to the problematic fact that constructor arguments must be optional).
@doctrinebot commented on GitHub (Dec 9, 2009):
Comment created by @beberlei:
Hm, i get your arguments, and you're right they are pros :-)
We definately need a good section on this problem in the manual, no matter how it turns out to be implemented.
@doctrinebot commented on GitHub (Feb 10, 2010):
Comment created by romanb:
Implemented.
@doctrinebot commented on GitHub (Feb 10, 2010):
Issue was closed with resolution "Fixed"