From a6b5ee3cd330ef537b8f48377e16f95280c074fb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 1 Aug 2010 16:37:53 +0200 Subject: [PATCH] Add Limitations and Known Issues Appendix to the manual --- manual/en.txt | 3 +- manual/en/change-tracking-policies.txt | 128 ++++++++++++++++ manual/en/limitations-and-known-issues.txt | 162 +++++++++++++++++++++ manual/en/partial-objects.txt | 49 +++++++ 4 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 manual/en/change-tracking-policies.txt create mode 100644 manual/en/limitations-and-known-issues.txt create mode 100644 manual/en/partial-objects.txt diff --git a/manual/en.txt b/manual/en.txt index 5c79a1bb2..460439100 100644 --- a/manual/en.txt +++ b/manual/en.txt @@ -22,4 +22,5 @@ + Improving Performance + Tools + Metadata Drivers -+ Best Practices \ No newline at end of file ++ Best Practices ++ Limitations and Known Issues \ No newline at end of file diff --git a/manual/en/change-tracking-policies.txt b/manual/en/change-tracking-policies.txt new file mode 100644 index 000000000..4f3746f9a --- /dev/null +++ b/manual/en/change-tracking-policies.txt @@ -0,0 +1,128 @@ +++ Change Tracking Policies + +Change tracking is the process of determining what has changed in managed +entities since the last time they were synchronized with the database. + +Doctrine provides 3 different change tracking policies, each having its +particular advantages and disadvantages. The change tracking policy can +be defined on a per-class basis (or more precisely, per-hierarchy). + ++++ Deferred Implicit + +The deferred implicit policy is the default change tracking policy and the most +convenient one. With this policy, Doctrine detects the changes by a +property-by-property comparison at commit time and also detects changes +to entities or new entities that are referenced by other managed entities +("persistence by reachability"). Although the most convenient policy, it can +have negative effects on performance if you are dealing with large units of +work (see "Understanding the Unit of Work"). Since Doctrine can't know what +has changed, it needs to check all managed entities for changes every time you +invoke EntityManager#flush(), making this operation rather costly. + ++++ Deferred Explicit + +The deferred explicit policy is similar to the deferred implicit policy in that +it detects changes through a property-by-property comparison at commit time. The +difference is that only entities are considered that have been explicitly marked +for change detection through a call to EntityManager#persist(entity) or through +a save cascade. All other entities are skipped. This policy therefore gives +improved performance for larger units of work while sacrificing the behavior +of "automatic dirty checking". + +Therefore, flush() operations are potentially cheaper with this policy. The +negative aspect this has is that if you have a rather large application and +you pass your objects through several layers for processing purposes and +business tasks you may need to track yourself which entities have changed +on the way so you can pass them to EntityManager#persist(). + +This policy can be configured as follows: + + [php] + /** + * @Entity + * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") + */ + class User + { + // ... + } + ++++ Notify + +This policy is based on the assumption that the entities notify interested +listeners of changes to their properties. For that purpose, a class that +wants to use this policy needs to implement the `NotifyPropertyChanged` +interface from the Doctrine\Common namespace. As a guideline, such an +implementation can look as follows: + + [php] + use Doctrine\Common\NotifyPropertyChanged, + Doctrine\Common\PropertyChangedListener; + + /** + * @Entity + * @ChangeTrackingPolicy("NOTIFY") + */ + class MyEntity implements NotifyPropertyChanged + { + // ... + + private $_listeners = array(); + + public function addPropertyChangedListener(PropertyChangedListener $listener) + { + $this->_listeners[] = $listener; + } + } + +Then, in each property setter of this class or derived classes, you need to +notify all the `PropertyChangedListener` instances. As an example we +add a convenience method on `MyEntity` that shows this behaviour: + + [php] + // ... + + class MyEntity implements NotifyPropertyChanged + { + // ... + + protected function _onPropertyChanged($propName, $oldValue, $newValue) + { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + + public function setData($data) + { + if ($data != $this->data) { + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +You have to invoke `_onPropertyChanged` inside every method that changes the +persistent state of `MyEntity`. + +The check whether the new value is different from the old one is not mandatory +but recommended. That way you also have full control over when you consider a +property changed. + +The negative point of this policy is obvious: You need implement an interface +and write some plumbing code. But also note that we tried hard to keep this +notification functionality abstract. Strictly speaking, it has nothing to do +with the persistence layer and the Doctrine ORM or DBAL. You may find that +property notification events come in handy in many other scenarios as well. +As mentioned earlier, the `Doctrine\Common` namespace is not that evil and +consists solely of very small classes and interfaces that have almost no +external dependencies (none to the DBAL and none to the ORM) and that you +can easily take with you should you want to swap out the persistence layer. +This change tracking policy does not introduce a dependency on the Doctrine +DBAL/ORM or the persistence layer. + +The positive point and main advantage of this policy is its effectiveness. It +has the best performance characteristics of the 3 policies with larger units of +work and a flush() operation is very cheap when nothing has changed. \ No newline at end of file diff --git a/manual/en/limitations-and-known-issues.txt b/manual/en/limitations-and-known-issues.txt new file mode 100644 index 000000000..135c1d2f1 --- /dev/null +++ b/manual/en/limitations-and-known-issues.txt @@ -0,0 +1,162 @@ +Much like every other piece of software Doctrine2 is not perfect and far from feature complete. +This section should give you an overview of current limitations of Doctrine 2 as well as known issues that +you should know about. We try to make using Doctrine2 a very pleasant experience. Therefore it is our believe +that stating the limitations to our users as early as possible is very important. + +The Known Issues section describes critical/blocker bugs and other issues that are either complicated to fix, +not fixable due to backwards compatibility issues or where no simple fix exists (yet). We don't +plan to add every bug in the tracker there, just those issues that can potentially cause nightmares +or pain of any sort. Luckily this section is empty right now! + +++ Current Limitations + +There is a set of limitations that exist currently which might be solved in the future. Any of this +limitations now stated has at least one ticket in the Tracker and is discussed for future releases. + ++++ Foreign Keys as Identifiers + +There are many use-cases where you would want to use an Entity-Attribute-Value approach to modelling and +define a table-schema like the following: + + [sql] + CREATE TABLE product ( + id INTEGER, + name VARCHAR, + PRIMARY KEY(id) + ); + + CREATE TABLE product_attributes ( + product_id INTEGER, + attribute_name VARCHAR, + attribute_value VARCHAR, + PRIMARY KEY (product_id, attribute_name) + ); + +This is currently *NOT* possible with Doctrine2. You have to define a surrogate key on the `product_attributes` +table and use a unique-constraint for the `product_id` and `attribute_name`. + + [sql] + CREATE TABLE product_attributes ( + attribute_id, INTEGER, + product_id INTEGER, + attribute_name VARCHAR, + attribute_value VARCHAR, + PRIMARY KEY (attribute_id), + UNIQUE (product_id, attribute_name) + ); + +Although we state that we support composite primary keys that does not currently include foreign keys as primary key +columns. To see the fundamental difference between the two different `product_attributes` tables you should see +how they translate into a Doctrine Mapping (Using Annotations): + + [php] + /** + * Scenario 1: THIS IS NOT POSSIBLE CURRENTLY + * @Entity @Table(name="product_attributes") + */ + class ProductAttribute + { + /** @Id @ManyToOne(targetEntity="Product") */ + private $product; + + /** @Id @Column(type="string", name="attribute_name") */ + private $name; + + /** @Column(type="string", name="attribute_value") */ + private $value; + } + + /** + * Scenario 2: Using the surrogate key workaround + * @Entity + * @Table(name="product_attributes", uniqueConstraints={@UniqueConstraint(columns={"product_id", "attribute_name"})})) + */ + class ProductAttribute + { + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + + /** @ManyToOne(targetEntity="Product") */ + private $product; + + /** @Column(type="string", name="attribute_name") */ + private $name; + + /** @Column(type="string", name="attribute_value") */ + private $value; + } + +The following Jira Issue currently contains the feature request to allow @ManyToOne and @OneToOne annotations +along the @Id annotation: [http://www.doctrine-project.org/jira/browse/DDC-117] + ++++ Mapping Arrays to a Join Table + +Related to the previous limitation with "Foreign Keys as Identifier" you might be interested in mapping the same +table structure as given above to an array. However this is not yet possible either. See the following example: + + [sql] + CREATE TABLE product ( + id INTEGER, + name VARCHAR, + PRIMARY KEY(id) + ); + + CREATE TABLE product_attributes ( + product_id INTEGER, + attribute_name VARCHAR, + attribute_value VARCHAR, + PRIMARY KEY (product_id, attribute_name) + ); + +This schema should be mapped to a Product Entity as follows: + + class Product + { + private $id; + private $name; + private $attributes = array(); + } + +Where the `attribute_name` column contains the key and `attribute_value` contains the value +of each array element in `$attributes`. + +The feature request for persistence of primitive value arrays [is described in the DDC-298 ticket](http://www.doctrine-project.org/jira/browse/DDC-298). + ++++ Value Objects + +There is currently no native support value objects in Doctrine other than for `DateTime` instances or if you +serialize the objects using `serialize()/deserialize()` which the DBAL Type "object" supports. + +The feature request for full value-object support [is described in the DDC-93 ticket](http://www.doctrine-project.org/jira/browse/DDC-93). + ++++ Applying Filter Rules to any Query + +There are scenarios in many applications where you want to apply additional filter rules to each query implicitly. Examples include: + +* In I18N Applications restrict results to a entities annotated with a specific locale +* For a large collection always only return objects in a specific date range/where condition applied. +* Soft-Delete + +There is currently no way to achieve this consistently across both DQL and Repository/Persister generated queries, but +as this is a pretty important feature we plan to add support for it in the future. + ++++ Custom Persisters + +A Perister in Doctrine is an object that is responsible for the hydration and write operations of an entity against the database. +Currently there is no way to overwrite the persister implementation for a given entity, however there are several use-cases that +can benefit from custom persister implementations: + +* [Add Upsert Support](http://www.doctrine-project.org/jira/browse/DDC-668) +* [Evaluate possible ways in which stored-procedures can be used](http://www.doctrine-project.org/jira/browse/DDC-445) +* The previous Filter Rules Feature Request + ++++ Order of Collections + +PHP Arrays are ordered hash-maps and so should be the `Doctrine\Common\Collections\Collection` interface. We plan +to evaluate a feature that optionally persists and hydrates the keys of a Collection instance. + +[Ticket DDC-213](http://www.doctrine-project.org/jira/browse/DDC-213) + +++ Known Issues + +There are currently no known critical/blocker or backward compatibility issues. \ No newline at end of file diff --git a/manual/en/partial-objects.txt b/manual/en/partial-objects.txt new file mode 100644 index 000000000..bb8f914b8 --- /dev/null +++ b/manual/en/partial-objects.txt @@ -0,0 +1,49 @@ +A partial object is an object whose state is not fully initialized after being +reconstituted from the database and that is disconnected from the rest of its data. +The following section will describe why partial objects are problematic and what the approach of Doctrine2 to this problem is. + +> **NOTE** +> The partial object problem in general does not apply to methods or +> queries where you do not retrieve the query result as objects. Examples are: +> `Query#getArrayResult()`, `Query#getScalarResult()`, `Query#getSingleScalarResult()`, +> etc. + +++ What is the problem? + +In short, partial objects are problematic because they are usually objects with +broken invariants. As such, code that uses these partial objects tends to be +very fragile and either needs to "know" which fields or methods can be safely +accessed or add checks around every field access or method invocation. The same +holds true for the internals, i.e. the method implementations, of such objects. +You usually simply assume the state you need in the method is available, after +all you properly constructed this object before you pushed it into the database, +right? These blind assumptions can quickly lead to null reference errors when +working with such partial objects. + +It gets worse with the scenario of an optional association (0..1 to 1). When +the associated field is NULL, you dont know whether this object does not have +an associated object or whether it was simply not loaded when the owning object +was loaded from the database. + +These are reasons why many ORMs do not allow partial objects at all and instead +you always have to load an object with all its fields (associations being proxied). +One secure way to allow partial objects is if the programming language/platform +allows the ORM tool to hook deeply into the object and instrument it in such a +way that individual fields (not only associations) can be loaded lazily on first +access. This is possible in Java, for example, through bytecode instrumentation. +In PHP though this is not possible, so there is no way to have "secure" partial +objects in an ORM with transparent persistence. + +Doctrine, by default, does not allow partial objects. That means, any query that +only selects partial object data and wants to retrieve the result as objects +(i.e. `Query#getResult()`) will raise an exception telling you that +partial objects are dangerous. If you want to force a query to return you partial +objects, possibly as a performance tweak, you can use the `partial` keyword as follows: + + [php] + $q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u"); + +++ When should I force partial objects? + +Mainly for optimization purposes, but be careful of premature optimization as partial objects +lead to potentially more fragile code. \ No newline at end of file