1
0
mirror of synced 2024-12-14 23:26:04 +03:00

Add Limitations and Known Issues Appendix to the manual

This commit is contained in:
Benjamin Eberlei 2010-08-01 16:37:53 +02:00
parent 93c061b7a5
commit a6b5ee3cd3
4 changed files with 341 additions and 1 deletions

View File

@ -22,4 +22,5 @@
+ Improving Performance
+ Tools
+ Metadata Drivers
+ Best Practices
+ Best Practices
+ Limitations and Known Issues

View File

@ -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.

View File

@ -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.

View File

@ -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.